How can I add properties to an object at runtime?

Solution 1:

It’s possible to add formal properties to a class via class_addProperty():

BOOL class_addProperty(Class cls,
    const char *name,
    const objc_property_attribute_t *attributes,
    unsigned int attributeCount)

The first two parameters are self-explanatory. The third parameter is an array of property attributes, and each property attribute is a name-value pair which follow Objective-C type encodings for declared properties. Note that the documentation still mentions the comma-separated string for the encoding of property attributes. Each segment in the comma-separated string is represented by one objc_property_attribute_t instance. Furthermore, objc_property_attribute_t accepts class names besides the generic @ type encoding of id.

Here’s a first draft of a program that dynamically adds a property called name to a class that already has an instance variable called _privateName:

#include <objc/runtime.h>
#import <Foundation/Foundation.h>

@interface SomeClass : NSObject {
    NSString *_privateName;
}
@end

@implementation SomeClass
- (id)init {
    self = [super init];
    if (self) _privateName = @"Steve";
    return self;
}
@end

NSString *nameGetter(id self, SEL _cmd) {
    Ivar ivar = class_getInstanceVariable([SomeClass class], "_privateName");
    return object_getIvar(self, ivar);
}

void nameSetter(id self, SEL _cmd, NSString *newName) {
    Ivar ivar = class_getInstanceVariable([SomeClass class], "_privateName");
    id oldName = object_getIvar(self, ivar);
    if (oldName != newName) object_setIvar(self, ivar, [newName copy]);
}

int main(void) {
    @autoreleasepool {
        objc_property_attribute_t type = { "T", "@\"NSString\"" };
        objc_property_attribute_t ownership = { "C", "" }; // C = copy
        objc_property_attribute_t backingivar  = { "V", "_privateName" };
        objc_property_attribute_t attrs[] = { type, ownership, backingivar };
        class_addProperty([SomeClass class], "name", attrs, 3);
        class_addMethod([SomeClass class], @selector(name), (IMP)nameGetter, "@@:");
        class_addMethod([SomeClass class], @selector(setName:), (IMP)nameSetter, "v@:@");

        id o = [SomeClass new];
        NSLog(@"%@", [o name]);
        [o setName:@"Jobs"];
        NSLog(@"%@", [o name]);
    }
}

Its (trimmed) output:

Steve
Jobs

The getter and setter methods should be written more carefully but this should be enough as an example of how to dynamically add a formal property at runtime.

Solution 2:

If you take a look at NSKeyValueCoding protocol, documented here, you can see that there is a message called:

- (id)valueForUndefinedKey:(NSString *)key

You should override that method to provide your custom result for the specified undefined property. Of course this assumes that your class uses the corresponding protocol.

This kind of approach is commonly uses to provide unknown behavior to classes (eg. a selector that doesn't exist).