在KVO observeValueForKeyPath中突变观察到的属性

时间:2013-06-10 13:23:56

标签: objective-c key-value-observing

我有一个KVO观察到NSMutableArray属性的视图控制器:

@interface MyViewController ()

@property (strong, nonatomic) NSMutableArray *values;

@end

@implementation MyViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.values = [NSMutableArray array];

    [self addObserver:self forKeyPath:@"self.values" options:0 context:nil];
}

- (void)dealloc
{
    [self removeObserver:self forKeyPath:@"self.values"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"self.values"])
    {
        // The code here is a bit more complex than that, but the main idea
        // is to mutate the object
        [self.values addObject:[NSDate date]]; // This line triggers a KVO notification, causing an infinite recursion 
    }
}

@end

我的实施目标是对阵列中的每个产品应用折扣,并在每次将产品添加到阵列时重新计算这些折扣。

我想在收到通知时修改被观察的属性,但显然上面的代码不起作用,它会进入无限递归。

一种解决方案是在observeValueForKeyPath...的开头删除观察者并在结尾处重新添加观察者。

这看起来有点难看,可能会导致问题是在observeValueForKeyPath...正在另一个线程上运行时,属性会被修改。

编辑:正如Wain所指出的,这里真正的问题是我的NSMutableArray self.value可能包含不可变对象(NSDictionnary s)而我没有用类似的东西替换它们的可变等价物:

[self.value replaceObjectAtIndex:0 withObject:[self.value[0] mutableCopy]];

不触发KVO通知。

1 个答案:

答案 0 :(得分:1)

如果您重视时间,请不要在致电observeValueForKeyPath:...期间添加或删除KVO观察结果。有关详细信息,请阅读我对this other question的回答。

如果您确实需要这样做,可以添加一个用作停止递归的标志的ivar。它可能看起来像这样:

@interface MyViewController ()

@property (strong, nonatomic) NSMutableArray *values;

@end

@implementation MyViewController
{
    BOOL _updatingValuesInObservation;
}

static void * const MyValueObservation = (void*)&MyValueObservation;

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.values = [NSMutableArray array];

    [self addObserver:self forKeyPath:@"values" options:0 context:MyValueObservation];
}

- (void)dealloc
{
    [self removeObserver:self forKeyPath:@"values" context:MyValueObservation];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (MyValueObservation == context && !_updatingValuesInObservation)
    {
        _updatingValuesInObservation = YES;
        [self.values addObject:[NSDate date]];
        _updatingValuesInObservation = NO; 
    }
    else
    {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

@end

还要注意使用context参数和调用super,这两种习惯都适用于更多的防弹代码。

最后,你不是在这里做这件事,但看起来你正在路上,所以我会提到它:请记住,观察一个集合与观察中的所有项目是不一样的/ em>一个集合。