在addObserver(KVO)中的上下文参数的最佳实践

我想知道当你观察一个属性时,你应该在KVO中设置上下文指针。 我刚刚开始使用KVO,而且我没有从文档中收集太多内容。 我在这个页面上看到: http : //www.jakeri.net/2009/12/custom-callout-bubble-in-mkmapview-final-solution/作者这样做:

[annView addObserver:self forKeyPath:@"selected" options:NSKeyValueObservingOptionNew context:GMAP_ANNOTATION_SELECTED]; 

然后在callback中,这是否:

 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ NSString *action = (NSString*)context; if([action isEqualToString:GMAP_ANNOTATION_SELECTED]){ 

我假设在这种情况下,作者只是在callback中创build一个string来识别。

然后在iOS 5推动极限书,我看到他这样做:

 [self.target addObserf:self forKeyPath:self.property options:0 context:(__bridge void *)self]; 

回电话:

 if ((__bridge id)context == self) { } else { [super observeValueForKeyPath .......]; } 

我想知道是否有一个标准或最佳做法传递到上下文指针? 谢谢!

重要的是(一般来说)你使用了一些东西 (而不是什么),并且你使用的任何东西都是独一无二

当你在你的类中有一个观测值,然后有人对你的类进行子类化时,就会发生这个主要的缺陷,并且会添加对同一个观察对象和相同keyPath的另一个观察值。 如果您的原始observeValueForKeyPath:...实现仅检查keyPath或观察object ,或者甚至两者,这可能不足以知道您的观察被callback。 使用其值为唯一且私有的context ,可以让您更确定给予observeValueForKeyPath:...的给定调用是您期望的调用。

例如,如果您只注册了didChange通知,但是子类注册了与NSKeyValueObservingOptionPrior选项相同的对象和keyPath,则这很重要。 如果您没有过滤调用observeValueForKeyPath:...使用context (或检查更改字典),您的处理程序将执行多次,当你只希望它执行一次。 不难想象这会如何引起问题。

我使用的模式是:

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

这个指针指向自己的位置,而且这个位置是唯一的(没有其他的静态或全局variables可以拥有这个地址,也没有任何堆或堆栈分配的对象有这个地址 – 这是一个非常强大的,虽然承认不是绝对的 ,保证),感谢链接器。 const使得编译器会警告我们,如果我们试图编写代码来改变指针的值,最后, static使得它对这个文件是私有的,所以在这个文件之外没有人可以获得对它的引用(再次,使它更有可能避免碰撞)。

我特别谨慎的一个模式是在问题中出现的一种模式:

 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSString *action = (NSString*)context; if([action isEqualToString:GMAP_ANNOTATION_SELECTED]) { 

context被声明为void* ,这意味着这就是关于它是什么的所有保证。 通过将其转换为NSString*您将打开一大堆潜在的不利因素。 如果其他人碰巧有一个注册使用NSString*作为context参数,那么当您将非对象值传递给isEqualToString:时,此方法将会崩溃。 指针相等(或者intptr_tuintptr_t相等)是唯一可以与context值一起使用的安全检查。

selfcontext是一种常见的方法。 这比什么都没有好,但是由于其他对象(更不用说子类)能够访问self的价值,并且可能将其用作context (导致模糊性),所以它比没有任何东西好,但是与上面提到的方法不同。

另外请记住,这不仅仅是可能导致陷阱的子类, 虽然可以说是一种罕见的模式,但没有什么能够阻止另一个对象注册您的对象以获得新的KVO观察结果。

为了提高可读性,您还可以将其封装在预处理macros中,如下所示:

 #define MyKVOContext(A) static void * const A = (void*)&A; 

KVO上下文应该是一个指向静态variables的指针,正如这个要点所表明的那样。 通常,我发现自己在做以下事情:

在我的文件的顶部附近ClassName.m我会ClassName.m

 static char ClassNameKVOContext = 0; 

当我开始观察targetObjectTargetClass一个实例)的aspect属性时,我会有

 [targetObject addObserver:self forKeyPath:PFXKeyTargetClassAspect options://... context:&ClassNameKVOContext]; 

其中PFXKeyTargetClassAspect是在TargetClass.m定义的等于@"aspect"并在TargetClass.h声明为externNSString * 。 (当然PFX只是你在项目中使用的前缀的占位符。)这给了我自动完成的好处,并保护我免受错别字。

当我完成对targetObject观察时,我会有

 [targetObject removeObserver:self forKeyPath:PFXKeyTargetClassAspect context:&ClassNameKVOContext]; 

为了避免在我的实现-observeValueForKeyPath:ofObject:change:context:过多的缩进,我喜欢写

 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context != &ClassNameKVOContext) { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; return; } if ([object isEqual:targetObject]) { if ([keyPath isEqualToString:PFXKeyTargetClassAspect]) { //targetObject has changed the value for the key @"aspect". //do something about it } } } 

我认为更好的办法是将其作为苹果的文档实现:

你的类中一个唯一命名的静态variables的地址是一个好的上下文。

 static void *PersonAccountBalanceContext = &PersonAccountBalanceContext; static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext; 

见文档 。

Interesting Posts