Obj-C easy method to convert from NSObject with properties to NSDictionary?

james Burns picture james Burns · Apr 25, 2012 · Viewed 19k times · Source

I ran across something that I eventually figured out, but think that there's probably a much more efficient way to accomplish it.

I had an object (an NSObject which adopted the MKAnnotation protocol) that had a number of properties (title, subtitle,latitude,longitude, info, etc.). I needed to be able to pass this object to another object, which wanted to extract info from it using objectForKey methods, as an NSDictionary (because that's what it was getting from another view controller).

What I ended up doing was create a new NSMutableDictionary and use setObject: forKey on it to transfer each piece of vital info, and then I just passed on the newly created dictionary.

Was there an easier way to do this?

Here's the relevant code:

// sender contains a custom map annotation that has extra properties...

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{    
    if ([[segue identifier] isEqualToString:@"showDetailFromMap"]) 
{
    DetailViewController *dest =[segue destinationViewController];

    //make a dictionary from annotaion to pass info
    NSMutableDictionary *myValues =[[NSMutableDictionary alloc] init];
    //fill with the relevant info
    [myValues setObject:[sender title] forKey:@"title"] ;
    [myValues setObject:[sender subtitle] forKey:@"subtitle"];
    [myValues setObject:[sender info] forKey:@"info"];
    [myValues setObject:[sender pic] forKey:@"pic"];
    [myValues setObject:[sender latitude] forKey:@"latitude"];
    [myValues setObject:[sender longitude] forKey:@"longitude"];
    //pass values
    dest.curLoc = myValues;
    }
}

Thanks in advance for your collective wisdom.


Here's what I came up with, thanks to the folks, below...

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{    
if ([[segue identifier] isEqualToString:@"showDetailFromMap"]) 
{
    DetailViewController *dest =[segue destinationViewController];
    NSArray *myKeys = [NSArray arrayWithObjects:
@"title",@"subtitle",@"info",@"pic",@"latitude",@"longitude", nil];

    //make a dictionary from annotaion to pass info
    NSDictionary *myValues =[sender dictionaryWithValuesForKeys:myKeys];

    //pass values
    dest.curLoc = myValues;
}

}

And a even simpler fix, as seen below...

Using valueForKey instead of object for key to retrieve the information.


Answer

Richard J. Ross III picture Richard J. Ross III · Apr 25, 2012

Sure thing! Use the objc-runtime and KVC!

#import <objc/runtime.h>

@interface NSDictionary(dictionaryWithObject)

+(NSDictionary *) dictionaryWithPropertiesOfObject:(id) obj;

@end
@implementation NSDictionary(dictionaryWithObject)

+(NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    unsigned count;
    objc_property_t *properties = class_copyPropertyList([obj class], &count);

    for (int i = 0; i < count; i++) {
        NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
        [dict setObject:[obj valueForKey:key] forKey:key];
    }

    free(properties);

    return [NSDictionary dictionaryWithDictionary:dict];
}

@end

And you would use like this:

MyObj *obj = [MyObj new];    
NSDictionary *dict = [NSDictionary dictionaryWithPropertiesOfObject:obj];
NSLog(@"%@", dict);