With the 'C' in its name, it's easy to forget that Objective-C is actually a dynamic language. This means that when a method is invoked, the calling code is not 'hard-wired' to the callee by the compiler & linker. Instead, the Objective-C Runtime examines the invocation and decides how it should proceed.
The most common use of this capability in regular Cocoa is in Core Data. You can create a class which is a subclass of NSManagedObject
and represents one of the entities in your data model. Individual attributes are represented as properties with a @dynamic
like this:
@interface Person : NSManagedObject @property (nonatomic, strong) NSDate *born; @end @implementation Person @dynamic born; @end
The Core Data framework is able to manage the mapping of these properties onto a persistent store while the user code can use the entities as if they were simple objects.
The @dynamic
keyword is simply a hint to the compiler that it should not raise any warnings if no implementation for the property getter and setter is provided.
When you invoke a method on an object, the runtime maps the selector of that method onto a concrete implementation and then executes the implementation. Typically, there are no getters or setters provided for a @dynamic
property so the runtime calls resolveInstanceMethod:
on your class instead. This method can examine the selector and provide a mapping to a C-style function using class_addMethod()
.
You return YES
from resolveInstanceMethod:
to indicate that you've resolved the method; otherwise the runtime will end up throwing an unrecognized selector exception. The runtime will also call resolveInstanceMethod:
if you call respondsToSelector:
or instancesRespondToSelector:
is invoked.
Apple Documentation: Dynamic Method Resolution
One good use for dynamic method resolution is when you find yourself writing boilerplate code (especially code which is duplicated for several properties). One common example of this in Cocoa apps is the access to NSUserDefaults which is often wrapped in a singleton like this:
// Preferences.h #import <Foundation/Foundation.h> @interface Preferences : NSObject { NSUserDefaults *_defaults; } @property (nonatomic, assign) BOOL autoStartBreak; // ... many, many more + (Preferences *)sharedInstance; @end
// Preferences.m #import "Preferences.h" @implementation Preferences - (id)init { if (self = [super init]) { _defaults = [NSUserDefaults standardUserDefaults]; } return self; } + (Preferences *)sharedInstance { static Preferences *sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[Preferences alloc] init]; }); return sharedInstance; } - (BOOL)autoStartBreak { return [_defaults boolForKey:@"autoStartBreak"]; } - (void)setAutoStartBreak:(BOOL)autoStartBreak { [_defaults setBool:autoStartBreak forKey:@"autoStartBreak"]; } // ... many, many more @end
Besides the tedium of writing almost identical code for every property, the other issue is that there are 4 opportunities for mistyping the property name which will cause it to fail without the compiler catching the bug (the getter and setter name and the two keyname instances).
By using dynamic method resolution, we can reduce the per-property code to 2 lines:
@property (nonatomic, assign) BOOL autoStartBreak; // ... @dynamic autoStartBreak;
When the getter or setter for autoStartBreak
is invoked, we dynamically add a method which uses NSUserDefaults to provide the persistence. To figure out which methods to use, we first have to examine the attributes of each property to figure out the return type (and setter type). This also gives us an opportunity to support custom getter and setter names.
The class_copyPropertyList()
method of the runtime allows us to examine the set of properties declared for a particular class. Continuing the example, once we determine we're dealing with a boolean property, we map the getter & setter onto these implementations:
BOOL paprefBoolGetter(id self, SEL _cmd) { NSString *selectorString = NSStringFromSelector(_cmd); PAPropertyDescriptor *propertyDescriptor = _dynamicProperties[selectorString]; return [[NSUserDefaults standardUserDefaults] boolForKey:propertyDescriptor.name]; } void paprefBoolSetter(id self, SEL _cmd, BOOL value) { NSString *selectorString = NSStringFromSelector(_cmd); PAPropertyDescriptor *propertyDescriptor = _dynamicProperties[selectorString]; [[NSUserDefaults standardUserDefaults] setBool:value forKey:propertyDescriptor.name]; [[NSNotificationCenter defaultCenter] postNotificationName:PAPreferencesDidChangeNotification object:self]; }
To see this technique extended to the full set of property types supported by NSUserDefaults, see: PAPreferences - https://github.com/dhennessy/PAPreferences
Published on Apr 5, 2014© Copyright 2009-2019 Peer Assembly Ltd.