bindings keypaths and collections

  • If a class declares an @property that's a reference to one of the collection classes (or a mutable variant thereof), such as NSArray, NSDictionary, NSSet, etc… can a key path (for bindings) refer to on object in that collection? If so, what's the syntax?

    Ex: my GameEngine class has an @property (nonatomic, strong) Player *player;

    The Player class, in turn, has an @property (nonatomic, strong) NSMutableArray *inventory;

    and the following method

    -(void)addThing:(Thing *)theThing;

    If I want to refer to a 'Thing' in the player's inventory using a key path, how would I do it?

    i.e. [[GameEngine sharedEngine] valueForKeyPath:@"player.inventory???"] to return an id pointer to some Thing object in the player's inventory (let's say I want the first one - [inventory objectAtIndex:0])?

    Can I do @"[player.inventory objectAtIndex:0]" as the path?
  • On Jul 29, 2012, at 11:15 , William Squires <wsquires...> wrote:

    > Ex: my GameEngine class has an @property (nonatomic, strong) Player *player;
    >
    > The Player class, in turn, has an @property (nonatomic, strong) NSMutableArray *inventory;
    >
    > and the following method
    >
    > -(void)addThing:(Thing *)theThing;
    >
    > If I want to refer to a 'Thing' in the player's inventory using a key path, how would I do it?
    >
    > i.e. [[GameEngine sharedEngine] valueForKeyPath:@"player.inventory???"] to return an id pointer to some Thing object in the player's inventory (let's say I want the first one - [inventory objectAtIndex:0])?
    >
    > Can I do @"[player.inventory objectAtIndex:0]" as the path?

    There's no magic path here, just think it through step by step:

    '[[GameEngine sharedEngine] valueForKeyPath:@"player.inventory"]' is a NSMutableArray. To get its first element, you'd write '[[[GameEngine sharedEngine] valueForKeyPath:@"player.inventory"] objectAtIndex: 0]'.

    But don't do it this way anyway. By declaring "inventory" to be a mutable array, your Player class has given up control of its data. Any client of the class can reach in from the outside and change the inventory without the class having a say in the matter. This is equivalent to declaring a NSMutableArray instance variable @public.

    The basic pattern I'd suggest is as follows:

    @property (nonatomic,readonly) NSArray *inventory;

    Note that this permits read-only, non-mutable access, thus hiding the internal class implementation from prying public eyes. If clients should be allowed to mutate the property, then implement the KVC mutation accessors in your class ('insertObject:inInventoryAtIndex:' etc), and clients can mutate the property like this:

    [[player mutableArrayValueForKey: @"inventory"] addObject: thing];

    and so on. Alternatively, provide a convenience property:

    @property (nonatomic,readonly) NSMutableArray *mutableInventory;

    that returns the 'mutableArrayValueForKey:' proxy, so that clients can write '[player.mutableInventory addObject: thing]'. Why bother mucking around with the proxy? Because it makes your "inventory" property KVO-compliant. Even if it happens not to matter right now, lack of KVO compliance will likely jump up and bite you later, and it's so easy to get it right now.

    Note that both of the above property definitions are 'readonly'. Normally it isn't necessary to have a setter for a collection property, because if a client of your class wants to replace the entire contents of the property it can do so this way:

    [player.mutableInventory removeAllObjects];
    [player.mutableInventory addObjectsFromArray: otherThings];

    or use one of the array 'replace' methods. If, for convenience, you really do want to provide setter-like behavior, then do this in place of the original definition:

    @property (nonatomic,copy) NSArray *inventory;

    and implement your setter to really copy the supplied array -- or, more likely, just do a simple removeAllObjects/addObjectsFromArray internally. Why do a copy, rather than having a '(nonatomic,strong)' property that just keeps the array it's given? Because in that case, something outside your class has a reference to the storage structure (the array) that your class is using, and can mutate it (destroying KVO-compliance, apart from the general havoc it could wreak on your class) at any time.

    P.S. Bindings and NSArrayControllers always mutate values using the 'mutableArrayValueForKey:' proxy anyway, so you don't need or want to specify your property as "mutableInventory" in IB. "inventory" works just fine.
previous month july 2012 next month
MTWTFSS
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
Go to today