1

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
james075
  • 1,280
  • 1
  • 12
  • 22
  • from JHTaskCollection who is a customized NSMutableArray – james075 Jun 03 '13 at 12:38
  • Does JHTaskCollection subclass NSMutableArray or does each instance have an NSMutableArray? – Nate Chandler Jun 03 '13 at 12:40
  • KVO on collection objects works differently. Follow this approach for [KVO on collection](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueCoding/Articles/AccessorConventions.html#//apple_ref/doc/uid/20002174-178830-BAJEDEFB) – Amar Jun 03 '13 at 12:45
  • @Amar is my collection implementation wrong ?, i followed the KVO on collection and it still doesn't work – james075 Jun 03 '13 at 12:58
  • @James03 Oops! I did not check the code you put in the edit. – Amar Jun 03 '13 at 13:00

3 Answers3

1

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
ipmcc
  • 29,581
  • 5
  • 84
  • 147
  • Another way is to use property which could simplify the code. `self.count = _tasks.count;` This would generate will/did change messages. – Tricertops Aug 03 '13 at 19:57
0

There's a couple of reasons why the notification for count isn't firing, both having to do with Key-Value Coding.

The first reason is because the count property is not updated when calling addTask:. There isn't a direct link between the count property and the count of the _tasks array. Any code manipulating the array has to be wrapped between -willChangeValueForKey: and -didChangeValueForKey: calls. However, the KVO protocol provides the ability to set dependent keys. Since the count property is affected by the array, you can set a dependency between these two keys by using

+ (NSSet*) keyPathsForValuesAffectingCount {
  return [NSSet setWithObject:@"tasks"];
}

Or the more generic + keyPathsForValuesAffectingValueForKey:(NSString *)key. Implementing either will fire notifications for count when tasks is modified.

The second reason why this won't work, is because you're not updating the tasks array in a Key-Value compliant way as defined in the Key-Value Coding Programming Guide. Basically, objects in ordered collections have to be added with either

-insertObject:in<Key>AtIndex:
-insert<Key>:atIndexes:

Or be wrapped with the will|didChangeValueForKey: calls.

In your example, if you provide the following implementation after setting the key dependency, you should observe a change.

- (void)addTask:(JHTask *)task
{
  [self insertObject:task inTasksAtIndex:[_tasks count]];
}
Dave FN
  • 652
  • 1
  • 14
  • 29
  • Ideal keypath that affects the count would be `"tasks.count"`, but **1.** he does not have `tasks` property, just a simple ivar and **2.** count of array is not observable. But in general your answer is the best approach to specify KVO dependency. – Tricertops Aug 03 '13 at 20:00
-2

-(NSInteger) count is a method, and as such, you cannot use KVO to monitor it - you could use AOP though I would think this is not the direction you would like to go.

Perhaps instead, you could directly monitor the collection, rather than it's count, though I'm not sure if you can do this with KVO (I don't think you can) - you would need to add an addObject: method and do your magic in there?

Edit:

You could use 'method swizzling', as laid out in this blog, though is it worth the added obscurity?

Community
  • 1
  • 1
user352891
  • 1,181
  • 1
  • 8
  • 14
  • KVO deals in keyPaths. It doesn't care whether something is a method or a property, or an ivar, or anything else. If OP wants count to be observable, they have to make it observable. But nothing about it being a method means you can't use KVO to monitor it. AOP is not the answer, and neither is method swizzling. – ipmcc Aug 03 '13 at 19:49