EmptyStack
EmptyStack

Reputation: 51374

How to set a value for my custom key (undefinedKey) in NSObject instance?

I have subclassed NSObject. And I need to set an object (NSMutableArray instance) to an undefined key (@"ItemsList") to that class's instance. How can I do that? For exmaple, I want to set,

[myObject setValue:myArray forUndefinedKey:@"ItemsList"];

What are the things I should do to make this work?

Upvotes: 3

Views: 3375

Answers (3)

Biasu
Biasu

Reputation: 498

@interface MyObject : NSObject

@end

@implementation MyObject {
    NSMutableDictionary*itemsList;
}
- (id)valueForUndefinedKey:(NSString*)key {
    if ([key isEqualToString:@"itemsList"]) {
        return itemsList;
    }
    else {
        return [super valueForUndefinedKey:key];
    }
}

- (void)setValue:(id)value forUndefinedKey:(NSString*)key {

    if ([key isEqualToString:@"itemsList"]) {
        itemsList = value;
    }
    else {
        [super setValue:value forUndefinedKey:key];
    }
}
@end

And for calling attribute, it's necessary to call it via valueForKey:

MyObject*myObject = [MyObject new];
[myObject setValue:@[@"item1", @"item2"] forKey:@"itemsList"];
NSArray*itemsList = [myObject valueForKey:@"itemsList"];

Upvotes: 0

Moszi
Moszi

Reputation: 3236

You can use a class like this, and subclass it. ( see the subclass below, and notice how the testProperty is not synthesized, but is used as dynamic )

@interface INDynamicPersister : NSObject {

}

/*!
    @method     saveObject:forKey:
    @abstract   Invoked when a property setter is invoked. 
*/
- (void) saveObject:(id)value forKey:(NSString *)key;


/*!
    @method     retrieveObjectForKey:
    @abstract   Invoked when a property getter needs its value.
*/
- (id) retrieveObjectForKey:(NSString *)key;

@end


#define Property_Setter_Suffix @":"

@implementation INDynamicPersister

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {

    NSMethodSignature *signature = [super methodSignatureForSelector:sel];

    if ( signature == nil ) {
        // get the name of the selector
        NSString *selectorName = [NSStringFromSelector(sel) lowercaseString];

        // if we have a setter property ... 
        if ( [selectorName hasSuffix:Property_Setter_Suffix] ) {
            signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
        }
        else {
            signature = [NSMethodSignature signatureWithObjCTypes:"@@:"];
        }
    }

    return signature;
}

/*!
    @method     forwardInvocation:
    @abstract   This method will handle the invocation for the property getters and setters.
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation {

    // get the name of the selector
    NSString *selectorName = [NSStringFromSelector([anInvocation selector]) lowercaseString];

    // if we have a setter property ... 
    if ( [selectorName hasSuffix:Property_Setter_Suffix] ) {
        int selectorLength = [selectorName length];

        NSString *propertyName = [selectorName substringWithRange:NSMakeRange(3, selectorLength - 4)];

        // get the value of the invocation
        id invocationValue;
        [anInvocation getArgument:&invocationValue atIndex:2];

        // now set the value for the property
        [self saveObject:invocationValue forKey:propertyName];
    }
    else {
        // if we have a getter property ... 
        id invocationReturnValue = [self retrieveObjectForKey:selectorName];

        [anInvocation setReturnValue:&invocationReturnValue];
    }
}

- (void) saveObject:(id)value forKey:(NSString *)key {
    [NSException raise:@"NotImplementedException" format:@"You need to override this method"];
}

- (id) retrieveObjectForKey:(NSString *)key {
    [NSException raise:@"NotImplementedException" format:@"You need to override this method"];

    return nil; // compiler is happy now
}


@end

The subclass could be something similar to this:

@interface MyDynamicEntity : INDynamicPersister {
    NSMutableDictionary *propertyValues;
}

@property (retain, nonatomic) NSString * testProperty;

@end


@implementation MyDynamicEntity

@dynamic testProperty;

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

    return self;
}

- (void) dealloc {
    [propertyValues release], propertyValues = nil;

    [super dealloc];
}

- (void) saveObject:(id)value forKey:(NSString *)key {
    if ( !value ) {
        [propertyValues removeObjectForKey:key];
    }
    else {
        [propertyValues setObject:value forKey:key];
    }
}

- (id) retrieveObjectForKey:(NSString *)key {
    return [propertyValues objectForKey:key];
}

@end

Hope this helps :)

Upvotes: 2

justin
justin

Reputation: 104698

here's one approach to easily accomplish this 'without having to create ivars':

@interface MONObject : NSObject
{
    NSMutableDictionary * stuff;
}
// ....
- (void)setValue:(id)value forCustomKey:(NSString *)key;

@end

@implementation MONObject

- (void)setValue:(id)value forCustomKey:(NSString *)key
{
    assert(self.stuff && value && key);
    [self.stuff setValue:value forKey:key];
}

@end

Upvotes: 3

Related Questions