Search code examples
ioscocoa-touchnsmutablearraykey-value-observingkvc

KVO on a collection


I added a observer to my collection, and observe the count on it

[[[JHTaskSave defaults] tasks] addObserver:self forKeyPath:@"count" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

JHTaskSave is a singleton object and tasks is a JHTaskCollection KVC compliant, when I add an object to my collection:

[[[JHTaskSave defaults] tasks] addTask:newTask]

The count of tasks changes but the observeValueForKeyPath is not called, I don't understand why

Here is my collection class:

@interface JHTaskCollection : NSObject <NSFastEnumeration>
{
    NSMutableArray      *_tasks;
}

@property (nonatomic) NSUInteger count;

- (id)taskAtIndex:(NSUInteger)index;
- (void)addTask:(JHTask *)task;
- (void)removeTask:(JHTask *)task;
- (void)insertObject:(id)key inTasksAtIndex:(NSUInteger)index;
- (void)removeObjectFromTasksAtIndex:(NSUInteger)index;
- (void)removeTaskAtIndexes:(NSIndexSet *)indexes;
- (NSArray *)taskAtIndexes:(NSIndexSet *)indexes;

@end


@implementation JHTaskCollection

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

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained *)stackbuf count:(NSUInteger)len
{
    return [_tasks countByEnumeratingWithState:state objects:stackbuf count:len];
}

- (NSArray *)taskAtIndexes:(NSIndexSet *)indexes
{
    return [_tasks objectsAtIndexes:indexes];
}

- (void)insertObject:(id)key inTasksAtIndex:(NSUInteger)index
{
    [_tasks insertObject:key atIndex:index];
}

- (void)removeObjectFromTasksAtIndex:(NSUInteger)index
{
    [_tasks removeObjectAtIndex:index];
}

- (void)removeTaskAtIndexes:(NSIndexSet *)indexes
{
    [_tasks removeObjectsAtIndexes:indexes];
}

- (JHTask *)taskAtIndex:(NSUInteger)index
{
    return [_tasks objectAtIndex:index];
}

- (NSUInteger)count
{
    return _tasks.count;
}

- (void)addTask:(JHTask *)task
{
    [_tasks addObject:task];
}

- (void)removeTask:(JHTask *)task
{
    [_tasks removeObject:task];
}

@end

Solution

  • Based on the code you posted, if you want count to be Key-Value Observable, you need to send willChangeValueForKey and didChangeValueForKey notifications any time you mutate the collection in a way that changes the count. Using your code:

    @implementation JHTaskCollection
    
    - (id)init
    {
        if(self = [super init]) {
            _tasks = [[NSMutableArray alloc] init];
        }
        return self;
    }
    
    - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained *)stackbuf count:(NSUInteger)len
    {
        return [_tasks countByEnumeratingWithState:state objects:stackbuf count:len];
    }
    
    - (NSArray *)taskAtIndexes:(NSIndexSet *)indexes
    {
        return [_tasks objectsAtIndexes:indexes];
    }
    
    - (void)insertObject:(id)key inTasksAtIndex:(NSUInteger)index
    {
        [self willChangeValueForKey: @"count"];
        [_tasks insertObject:key atIndex:index];
        [self didChangeValueForKey: @"count"];
    }
    
    - (void)removeObjectFromTasksAtIndex:(NSUInteger)index
    {
        [self willChangeValueForKey: @"count"];
        [_tasks removeObjectAtIndex:index];
        [self didChangeValueForKey: @"count"];
    }
    
    - (void)removeTaskAtIndexes:(NSIndexSet *)indexes
    {
        [self willChangeValueForKey: @"count"];
        [_tasks removeObjectsAtIndexes:indexes];
        [self didChangeValueForKey: @"count"];
    }
    
    - (JHTask *)taskAtIndex:(NSUInteger)index
    {
        return [_tasks objectAtIndex:index];
    }
    
    - (NSUInteger)count
    {
        return _tasks.count;
    }
    
    - (void)addTask:(JHTask *)task
    {
        [self willChangeValueForKey: @"count"];
        [_tasks addObject:task];
        [self didChangeValueForKey: @"count"];
    }
    
    - (void)removeTask:(JHTask *)task
    {
        [self willChangeValueForKey: @"count"];
        [_tasks removeObject:task];
        [self didChangeValueForKey: @"count"];
    }
    
    @end