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:
{% highlight objc %} #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
{% endhighlight %}
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