Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using NSMutableDictionary as backing store for properties

I am looking for a shorthand way of setting my properties directly to an NSMutableDictionary that is a instance variable. ie:

KVCModle.h:

@interface KVModel : NSObject {
    NSMutableDictionary * data;
}
@property(nonatomic,assign)NSString * string1;
@property(nonatomic,assign)NSString * string2;
@end

KVCModel.m

#import "KVModel.h"


@implementation KVModel

-(id)init
{
    self = [super init];
    if(self)
    {
        data = [[NSMutableDictionary alloc] init];
    }
    return self;
}

-(NSString *)string1
{
    return [data objectForKey:@"string1"];
}
-(NSString *)string2
{
    return [data objectForKey:@"string2"];
}
-(void)setString1:(NSString *)_string1
{
    [data setObject:_string1 forKey:@"string1"];
}
-(void)setString2:(NSString *)_string2
{
    [data setObject:_string2 forKey:@"string2"];
}
-(void)dealloc
{
    [data release];
    [super dealloc];
}

@end

I have tried to override setValue:ForKey: and valueForKey:, but those aren't called, they allow you to directly set properties without using the property syntax.

I have made preprocessor macros to make this work in the past, but I am not interested in typing at all, and would like to avoid as much of it as I can in the future. Is there a way to make this work that I am not familiar with?

I have thought about using NSManagedObject, but I am not sure if I can get what I want out of that. EDIT: source

like image 914
Grady Player Avatar asked Dec 04 '22 08:12

Grady Player


2 Answers

If you're trying to access the properties with code like foo = obj.foo and obj.foo = foo, that's why it doesn't work.

Property-access syntax is synonymous with message syntax; the former is exactly the same as foo = [obj foo], and the latter is exactly the same as [obj setFoo:foo]. There is no KVC code to intercept. Properties are at the language level; KVC is at the framework level.

You'll need to intercept the accessor messages instead. Consider implementing the resolveInstanceMethod: class method, in which you “resolve” the selector by adding a method implementation to the class using the Objective-C runtime API. You can add the same implementation(s) for many different selectors.

For your purpose, have a function or method that examines the selector (using NSStringForSelector and regular NSString-examining techniques) and returns two facts: (1) the property name, and (2) whether it's a getter (foo, isFoo) or setter (setFoo:). Then, have two more methods, one a dynamic getter and the other a dynamic setter. When the selector names a getter, add it with your dynamic-getter method; when the selector names a setter, add it with your dynamic-setter method.

So how do the dynamic-getter and -setter methods work? They'll need to know what property to dynamically get and set, but they also need to take no arguments (getter) or one argument (setter, which takes the value), in order to match the original property-access message. You might be wondering how these generic implementations can know what property to get or set. The answer is: It's in the selector! The selector used to send the message is passed to the implementation as the hidden argument _cmd, so examine that selector the same way as before to extract the name of the property you should dynamically get or set. Then, the dynamic getter should send [data objectForKey:keyExtractedFromSelector] and the dynamic setter should send [data setObject:newValue forKey:keyExtractedFromSelector].

Two caveats:

  1. You may still get complaints from the compiler when you use the property-access syntax to access a “property” that you have not declared in the class's @interface. This is normal and intentional; you're really only supposed to use property-access syntax to access known formal properties. What you're doing, while I found it fun to solve, is technically an abuse of the property-access syntax.
  2. This will only work for object values. KVC does the boxing and unboxing for primitive values, such as integers; since KVC is not involved, no free boxing and unboxing. If you have declared formal properties (see 1), you'll need to introspect them using the Objective-C runtime API, and do the boxing and unboxing yourself with your findings.
like image 197
Peter Hosey Avatar answered Jan 05 '23 15:01

Peter Hosey


This piqued my curiosity, so I went ahead and used Peter Hosey's suggestion of overriding +resolveInstanceMethod: to generate the getters and setters. I posted the resulting object (DDDynamicStorageObject) to a github repository:

https://github.com/davedelong/Demos

like image 36
Dave DeLong Avatar answered Jan 05 '23 15:01

Dave DeLong