Skip to content

Instantly share code, notes, and snippets.

@woolsweater
Created April 1, 2015 23:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save woolsweater/4fb874b15449ee7fd7e8 to your computer and use it in GitHub Desktop.
Save woolsweater/4fb874b15449ee7fd7e8 to your computer and use it in GitHub Desktop.
Backing ObjC class's properties with a dictionary automatically.
/*
* Demo of backing class properties with a dictionary instead of individual
* ivars.
*
* http://stackoverflow.com/questions/29158157/
*
* Created by Josh Caswell on 3/19/15.
* Released to the public domain on that date.
*/
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
NSString * setterNameForGetterName(NSString * getterName)
{
NSString * first = [getterName substringToIndex:1];
NSString * rest = [getterName substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",
[first uppercaseString], rest];
}
NSString * propNameFromSetterName(NSString * setterName)
{
NSString * first = [setterName substringWithRange:(NSRange){3, 1}];
NSRange restRange = (NSRange){4, [setterName length]-5};
NSString * rest = [setterName substringWithRange:restRange];
return [NSString stringWithFormat:@"%@%@", [first lowercaseString], rest];
}
@interface DictBack : NSObject
/* Sample properties.
* N.B. That the ivars are _still created_. They're just not used.
*/
@property (strong, nonatomic) NSString * userID;
@property (strong, nonatomic) NSNumber * privilegeLevel;
- (void)readOutAttributes;
@end
@implementation DictBack
{
NSMutableDictionary * attributes;
}
/* Both of these rely on the accessor names being standard:
* -set<PropName>: and -<propName>
*/
id redirectedGetter(DictBack * self, SEL _cmd)
{
NSString * propName = NSStringFromSelector(_cmd);
// NSLog(@"Getting %@", propName);
return self->attributes[propName];
}
void redirectedSetter(DictBack * self, SEL _cmd, id val)
{
NSString * propName = propNameFromSetterName(NSStringFromSelector(_cmd));
// NSLog(@"Setting %@", propName);
self->attributes[propName] = val;
}
+ (void)initialize
{
// Prevent subclasses from being drawn into this crazy scheme.
if( self != [DictBack class] ) return;
unsigned int numProps;
// class_copyPropertyList() only returns properties declared in the
// given class, not anything inherited, so this is safe.
objc_property_t * props = class_copyPropertyList(self, &numProps);
for( unsigned int i = 0; i < numProps; i++ ){
objc_property_t prop = props[i];
const char * attrs = property_getAttributes(prop);
NSString * getterName = [NSString stringWithUTF8String:property_getName(prop)];
class_replaceMethod(self, NSSelectorFromString(getterName),
(IMP)redirectedGetter, attrs);
NSString * setterName = setterNameForGetterName(getterName);
class_replaceMethod(self, NSSelectorFromString(setterName),
(IMP)redirectedSetter, attrs);
}
}
- (id)init {
self = [super init];
if( !self ) return nil;
attributes = [NSMutableDictionary dictionary];
return self;
}
- (void)readOutAttributes
{
NSLog(@"%@", attributes);
}
@end
int main(int argc, const char * argv[])
{
@autoreleasepool {
DictBack * d = [DictBack new];
// Set properties; this goes through to the dictionary.
d.userID = @"Alistair";
d.privilegeLevel = @7;
[d readOutAttributes];
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment