【问题标题】:KVO on a collection集合上的 KVO
【发布时间】:2013-06-03 12:34:43
【问题描述】:

我在我的收藏中添加了一个观察者,并观察它的计数

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

当我将对象添加到我的集合时,JHTaskSave 是一个单例对象,而 tasks 是一个符合 JHTaskCollection KVC 的对象:

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

任务计数发生变化但没有调用observeValueForKeyPath,我不明白为什么

这是我的收藏类:

@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

【问题讨论】:

  • 来自自定义 NSMutableArray 的 JHTaskCollection
  • JHTaskCollection 是 NSMutableArray 的子类还是每个实例都有一个 NSMutableArray?
  • 集合对象上的 KVO 工作方式不同。按照这种方法处理KVO on collection
  • @Amar 是我的集合实现错误吗?,我在集合上遵循了 KVO,但它仍然不起作用
  • @James03 哎呀!我没有检查您在编辑中输入的代码。

标签: ios cocoa-touch nsmutablearray key-value-observing kvc


【解决方案1】:

根据您发布的代码,如果您希望 count 成为 Key-Value Observable,您需要在任何时候以更改计数的方式更改集合时发送 willChangeValueForKeydidChangeValueForKey 通知。使用您的代码:

@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

【讨论】:

  • 另一种方法是使用可以简化代码的属性。 self.count = _tasks.count; 这将生成将/确实更改消息。
【解决方案2】:

count 的通知没有触发有几个原因,都与键值编码有关。

第一个原因是因为调用addTask:count属性没有更新。 count 属性和 _tasks 数组的计数之间没有直接联系。任何操作数组的代码都必须包含在 -willChangeValueForKey:-didChangeValueForKey: 调用之间。然而,KVO 协议提供了设置依赖键的能力。由于count属性受数组影响,可以通过使用

来设置这两个key之间的依赖关系
+ (NSSet*) keyPathsForValuesAffectingCount {
  return [NSSet setWithObject:@"tasks"];
}

或者更通用的+ keyPathsForValuesAffectingValueForKey:(NSString *)key。当tasks 被修改时,实现任一将触发count 的通知。

这不起作用的第二个原因是因为您没有以Key-Value Coding Programming Guide 中定义的键值兼容方式更新tasks 数组。基本上,有序集合中的对象必须添加任一

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

或者被will|didChangeValueForKey: 调用包裹。

在您的示例中,如果您在设置键依赖后提供以下实现,您应该观察到变化。

- (void)addTask:(JHTask *)task
{
  [self insertObject:task inTasksAtIndex:[_tasks count]];
}

【讨论】:

  • 影响计数的理想键路径是"tasks.count",但1。他没有tasks属性,只有一个简单的ivar和2。 数组计数不可观察。但总的来说,您的答案是指定 KVO 依赖项的最佳方法。
【解决方案3】:

-(NSInteger) count 是一种方法,因此,您不能使用 KVO 来监控它 - 您可以使用 AOP 尽管我认为这不是您想要的方向。

也许相反,您可以直接监视集合,而不是计数,尽管我不确定您是否可以使用 KVO 执行此操作(我认为您不能) - 您需要添加一个 addObject: 方法并在那里施展你的魔法?

编辑:

您可以使用 this blog 中所述的“方法调配”,但值得增加晦涩难懂吗?

【讨论】:

  • KVO 处理 keyPaths。它不关心某个东西是方法还是属性,或者是 ivar,还是其他任何东西。如果 OP 希望 count 可观察,他们必须使其可观察。但它作为一种方法并不意味着你不能使用 KVO 来监控它。 AOP 不是解决方案,方法调配也不是。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-07
  • 1970-01-01
相关资源
最近更新 更多