Objective-C as a dynamic language

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.

How @dynamic Works

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

When to use dynamic language features

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).

A better way to handle preferences

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.