Objective-C Runtime

It is pretty rare to actually have to dive into the objc-runtime for any day to day coding. Most developers wont have to touch the runtime, however it is helpful to know what is possible and be able to use it if required. The objective-c runtime is written in C and is how the underlying parts of the objective-c language work including message sending, ivars and properties. This post shows an example of where I have used the Objective-C runtime in one of my projects.

One example of where I have used the runtime in my projects is the validation code in DBValidator. The validation code is implemented as a category on NSObject called NSObject+DBValidator. This is so we can add validation rules to any objective-c object. The only problem with this approach is that you can’t add any properties or ivars to an object using a category.

We can work around this limitation by using the objective-c runtime directly.

Below is the implementation of the NSObject+DBValidator category:

#import "NSObject+DBValidator.h"
#import <objc/runtime.h>

#define VALIDATION_RULES_KEY @"validationruleskey"

@implementation NSObject (DBValidator)

-(void) setValidationRules:(NSMutableArray *)validationRules {
    objc_setAssociatedObject(self, VALIDATION_RULES_KEY, validationRules, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(NSMutableArray*) validationRules {
    NSMutableArray *validationRules = objc_getAssociatedObject(self, VALIDATION_RULES_KEY);
    if (!validationRules)
        validationRules = [NSMutableArray array];
    return validationRules;
}

-(void) addValidationRule: (DBValidationRule*) validationRule {

    NSMutableArray *validationRules = self.validationRules;
    if (validationRule)
        [validationRules addObject:validationRule];
    self.validationRules = validationRules;
}

-(void) removeAllValidationRules {
    NSMutableArray *validationRules = self.validationRules;
    [validationRules removeAllObjects];
    self.validationRules = validationRules;
}

-(NSMutableArray*) validate {
    NSMutableArray *failureMessages = [NSMutableArray array];
    for (DBValidationRule *rule in self.validationRules) {
        BOOL isValid = [rule passesValidation];
        if (!isValid)
             [failureMessages addObject: rule.failureMessage];
    }
    return failureMessages;
}

@end

We have a @property defined in the header called validationRules and we override both the setter and getter in the implementation. In the setValidationRules: method we use a C function from the objective-c runtime called objc_setAssociatedObject. This function allows us to set a reference on the self object. We give it a key, the object (in this case the validationRules passed to the method) and the association policy.

The valid options for the association policy are:

enum {
   OBJC_ASSOCIATION_ASSIGN = 0,
   OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
   OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
   OBJC_ASSOCIATION_RETAIN = 01401,
   OBJC_ASSOCIATION_COPY = 01403
};

Notice how these options map directly to @property storage options! We are using OBJC_ASSOCIATION_RETAIN_NONATOMIC because we want our NSObject to retain the validation rules that are set on it.

In our validationRules method, we use a similar call from the objective-c runtime called objc_getAssociatedObject. This allows us to retrieve the object we set a reference to in the preious method. We have to pass the parent object and the key for the associated object we want. We return an empty array if validation rules are not yet set for this object.

Check out the full source code in the DBValidator GitHub Project

Advertisements