PDA

View Full Version : NSDictionary as backing store to Properties




faffoo
Aug 2, 2011, 10:14 AM
Hi there,

I have an NSDictionary as an instance variable, and want to be able to access the values in it by properties (dot syntax).

I am aware you cannot subclass NSDictionary, so I realised I need to create a new class and create properties on it.

So if in my header file I have:
@interface MyTest : NSObject
@property (nonatomic, readonly) NSString *name;
@end

Is there any easy way to intercept this request (i.e. something = myObject.name; or myObject.name = something) before an unrecognised selector is thrown.

My usage of this would typically be:
-(NSString *)name {
return [dictionary objectForKey:@"name"];
}
but without having to create a method for every signature. All help appreciated. Kind regards
Matthew Casey



robbieduncan
Aug 2, 2011, 10:47 AM
The answer to this is maybe. I've not tried this an, in all honesty, I'm not sure if it's a good idea. But you should certainly give it a go :D

First of all if you are not going to actually provide the name and setName (for example) methods you will probably want to look at overriding respondsToSelector: (http://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Protocols/NSObject_Protocol/Reference/NSObject.html#//apple_ref/occ/intfm/NSObject/respondsToSelector:). Once you have done that so your instances look like they respond to the selectors you want you can probably use the exceptionally sketchy sounding resolveInstanceMethod: (http://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html#//apple_ref/occ/clm/NSObject/resolveInstanceMethod:) to point all these selectors at a single method in your code.

I think we can get around the issue of not wanting to code accessors for each and every property using @dynamic

Now the next bit is all typed straight into this window so probably won't work. But it shows you what I'm thinking.

.h

@interface MyTest : NSObject
{
NSMutableDictionary *properties;
}
@property (nonatomic, readonly) NSString *name;
@end


.m

// Assume normal init to create/load the propeties array

@dynamic(name);

- (BOOL)respondsToSelector:(SEL)aSelector
{
if (aSelector == @selector(name))
{
return YES;
}
[super respondsToSelector:aSelector];
}

- (id)dictionaryValueForKey:(NSString *) key
{
return [properties objectForKey:key];
}

// This is the function that will handle the dictionary access for get mode
id dynamicGet(id self, SEL _cmd)
{
return [self dictionaryValueForKey:NSStringFromSelector(_cmd)];
}

+ (BOOL)resolveInstanceMethod:(SEL)aSel
{
if (aSel == @selector(name))
{
class_addMethod([self class], aSEL, (IMP) dynamicGet, "@@:"); // See http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 for type encodings. This says the method returns an object and has two parameters and object and a SEL
return YES;
}
return [super resolveInstanceMethod:aSel];
}


Note that the keys in the properties dictionary must match the pseudo property names exactly. So in this case the key must be @"name". The set version would be a little more complicated as you would have to have a different method signature and remove the set from the selector.

You could make it a bit more robust by, for example, always lower-casing the key so you could use .Name or .NAME and still hit the @"name" key in the dictionary.