The Objective-C runtime allows you to look under the hood of your code and play with it while it is being executed. You can see the properties and methods of a class, it’s super class, and the methods and properties from a protocol. You can even change the way your program works at runtime by creating a new class, changing it’s super class or swizzling it’s methods.

After about two years of iOS development I really got tired of integrating web services into my application. If a login request succedes it would return something similar to this response:

{ "status" : "success", "user" : { "first_name" : "Andrei", "last_name" : "Puni", "age" : 22 } }

After handling the networking part we usually get a NSDictionary with the values from the json/xml respose. Now all we need to do is write the User class and create a User instance from that :

// User.h @interface User : NSObject @property (nonatomic, retain) NSString *firstName; @property (nonatomic, retain) NSString *lastName; @property (nonatomic, retain) NSNumber *age; + (instancetype)userFromInfo:(NSDictionary *)info; @end

// User.m @implementation User + (instancetype)createFromInfo:(NSDictionary *)info { User *user = [self new]; user.firstName = info[@"first_name"]; user.lastName = info[@"last_name"]; user.age = info[@"age"]; return user; } @end

This code is pretty simple and does the job, but it has some issues:

if you add or remove a property from the model you must also mirror the changes in the userFromInfo method

method you have to write code everytime you create a new model

If we would have a NSArray with the name of the properties of the model (for User it would be @[@"firstName", @"lastName", @"age"] ) this code would be a bit different:

// User.m // code from: http://stackoverflow.com/questions/1918972/camelcase-to-underscores-and-back-in-objective-c NSString *CamelCaseToUnderscores(NSString *input) { NSMutableString *output = [NSMutableString string]; NSCharacterSet *uppercase = [NSCharacterSet uppercaseLetterCharacterSet]; for (NSInteger idx = 0; idx < [input length]; idx += 1) { unichar c = [input characterAtIndex:idx]; if ([uppercase characterIsMember:c]) { [output appendFormat:@"%s%C", (idx == 0 ? "" : "_"), (unichar)(c & ~0×20)]; } else { [output appendFormat:@"%C", c]; } } return output; } @implementation User - (NSArray *)propertyList { return @[@"firstName", @"lastName", @"age"]; } + (instancetype)createFromInfo:(NSDictionary *)info { NSObject *object = [self new]; for (NSString *property in object.propertyList) { id value = info[property]; // look for the underscore form "firstName" -> "first_name" if (value == nil) { value = info[CamelCaseToUnderscores(property)]; } [object setValue:value forKey:property]; } return object; } @end

All we have to do now is get the class property list and move this code in a category for NSObject and never write the same code again.

#import <objc/runtime.h> ... - (NSArray *)propertyList { Class currentClass = [self class]; NSMutableArray *propertyList = [NSMutableDictionary array]; // class_copyPropertyList does not include properties declared in super classes // so we have to follow them until we reach NSObject do { unsigned int outCount, i; objc_property_t *properties = class_copyPropertyList(currentClass, &outCount); for (i = 0; i < outCount; i++) { objc_property_t property = properties[i]; NSString *propertyName = [NSString stringWithFormat:@"%s", property_getName(property)]; [propertyList addObject:propertyName]; } free(properties); currentClass = [currentClass superclass]; } while ([currentClass superclass]); return propertyInfo; }

Now that we have this magic method, the code will look like this:

// User. @interface User : NSObject @property (nonatomic, retain) NSString *firstName; @property (nonatomic, retain) NSString *lastName; @property (nonatomic, retain) NSNumber *age; @end ... // User.m @implementation User @end ... AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; [manager GET:@"http://example.com/login.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { User *user = [User createFromInfo:responseObject]; // do something with user } failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(@"Error: %@", error); }];

The reverse function is easy to implement and allows (de)serializing almost any object (as long as all the properties are objects). You can find these methods with some optimizations and tweaks in my cocoapod. There you will also find a lot of convenience methods and other useful code snippets that I use in most of my projects.

Hope you liked the hack, I’m looking forward to seeing your thoughts in the comments!