Search code examples
iphoneobjective-ccocoa-touchkey-value-coding

On Objective-C/Cocoa Key-Value coding and arrays


I'm trying to work out the "correct" way to handle populating an array with key-value coding for an iPhone app. I've come up with something that works, but it's fairly hackish. Basically I'm parsing an XML document into a set of code-generated models. Let's assume the XML is of this format:

<foo>
    <bar>
        <item name="baz" />
        <item name="bog" />
    </bar>
</foo>

On my generated object that represents the Bar element, I have an NSMutableArray defined for the sub-node:

@interface Bar : NSObject {
    NSMutableArray *item;
}
@end

So by default when I call setValue:forKey: on an instance of Bar, it ends up overwriting the instance of NSMutableArray with a single instance of the Item object. What I've currently done to get this working is where it gets hacky. I renamed the array instance variable to be something else, let's say the plural form of the name:

@interface Bar : NSObject {
    NSMutableArray *items;
}
@end

This causes the default accessor for setValue:forKey: to miss. I then added this method to the Bar implementation:

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    if([key isEqualToString:@"item"]) {
        if(items == nil) {
            items = [[NSMutableArray alloc] init];
            [items retain];         
        }
        [items addObject:value];
    }
}

And everything works! I'm sure there must be a better way to do this though! I've read through the Key-Value Coding Programming Guide, but I must be missing something, because I'm unclear as to how things are supposed to work for array accessors. I've tried implementing countOf: and objectInAtIndex: as they KVC Programming guide seems to indicate, but that still results with my NSMutableArray being overwritten with an instance of the Item type.


Solution

  • If you want to use KVC properties for an array you should look into the documentation for mutableArrayValueForKey:

    Basically with your class:

    @interface Bar : NSObject {
        NSMutableArray *items;
    }
    @end
    

    You would define these methods as a minimum, assuming you initialized your array elsewhere:

    - (void)insertObject:(id)object inItemsAtIndex:(NSUInteger)index {
        [items insertObject:object atIndex:index];
    }
    
    - (void)removeObjectFromItemsAtIndex:(NSUInteger)index {
        [items removeObjectAtIndex:index];
    }
    
    - (NSArray *)items {
        /* depending on how pedantic you want to be
           possibly return [NSArray arrayWithArray:items] */
        return items;
    }
    

    Once you've implemented those methods you can call [bar mutableArrayValueForKey:@"items"]. It will return an NSMutableArray proxy object that you can add and remove objects from. That object will in turn generate the appropriate KVO messages and uses the methods you just defined to interact with your actual array.