开发者

Accessing collection through KVC (to protect collection and be KVO compliant)

I have a class Test which has an array of Foos. I want to provide access to the Foos without exposing the ivar directly. I'm trying to make this KVC compliant (also to pave the way for KVO compliance). I have:

Test.h

@interface Test : NSObject
{
    NSMutableArray *foos;
}

@property (readonly, copy) NSMutableArray *foos;

@end

Test.m

- (id) init
{
    self = [super init];
    if (self != nil)
    {
        foos = [[NSMutableArray array] retain];
    }
    return self;
}

- (NSMutableArray*) foos
{
    return [self mutableArrayValueForKey:@"foos"];
}

- (NSUInteger)countOfFoos
{
    return [foos count];
}

- (id)objectInFoosAtIndex:(NSUInteger)index
{
    return [foos objectAtIndex:index];
}

- (NSArray *)foosAtIndexes:(NSIndexSet *)indexes
{
    return [foos objectsAtIndexes:indexes];
}

- (void)insertObject:(id)key inFoosAtIndex:(NSUInteger)index
{
    [foos inser开发者_开发知识库tObject:key atIndex:index];
}

- (void)insertFoos:(NSArray *)foosArray atIndexes:(NSIndexSet *)indexes
{
    [foos insertObjects:foosArray atIndexes:indexes];
}

- (void)removeObjectFromFoosAtIndex:(NSUInteger)index
{
    [foos removeObjectAtIndex:index];
}

- (void)removeFoosAtIndexes:(NSIndexSet *)indexes
{
    [foos removeObjectsAtIndexes:indexes];
}

This enters an infinite loop when a client tries to add a Foo:

Test *test = [[Test alloc] init];
NSMutableArray *foos = test.foos;
[foos addObject:@"adding object"]; // infinite loop here

What am I doing wrong?


- (NSMutableArray*) foos
{
    return [self mutableArrayValueForKey:@"foos"];
}

An accessor should not use KVC to get the value of the property being accessed; the idea is that KVC goes through the accessors, because the accessors are closer to the value than KVC is.

The correct implementation of foos should return a copy, mutable or otherwise, of the array. Here's how I'd do it:

- (NSArray *) foos
{
    return [[foos copy] autorelease];
}

I would also make all of the accessors public. Anything that wants to mutate the array or randomly access elements at specific indexes can do so that way. It's still safe and encapsulated because they're going through your accessors, not directly accessing the array.

There's not really any reason to use the KVC protocol methods yourself unless you don't know what key you'll access at the time you write the code. For example, if you were writing the nib loader or the Cocoa Bindings system, you would use KVC.


The problem is that the proxy NSMutableArray returned by mutableArrayValueForKey: first has to get the real array, which it does through the "foos" method. Since that's the one that returns a proxy NSMutableArray it enters an infinite loop. One solution is to use another name:

- (NSMutableArray*) mutableFoos
{
    return [self mutableArrayValueForKey:@"foos"];
}


I spent a very long time on this problem and wanted to get this through an accessor. I wanted to clarify in the answer for those coming in. This is what I did:

@property (nonatomic,readonly,getter=getTheFoos) NSMutableArray* foos;

Then obviously implemented:

- (NSMutableArray*)getTheFoos {
    return [self mutableArrayValueForKey:@"foos"];
}

Had to be careful though, getFoos appears to be an (undocumented) KVC accessor, because this sends it into the same loop.

Then onto KVO:

Test* test= [[Test alloc] init];
NSObject* obj= [[NSObject alloc] init];
NSMutableArray* arrTheData= test.foos;

[test.foos insertObject:obj atIndex:0];
[arrFoos insertObject:obj atIndex:0];

arrFoos can read the updated, mutated array (it will have two objects in it), but inserting into it will not fire KVO. Somewhere on my adventures, I saw that the return from mutableArrayValueForKey: doesn't return an NSMutableArray*, but a subclass of it, which might be the cause of it.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜