在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_t
或uintptr_t
相等)是唯一可以与context
值一起使用的安全检查。
以self
为context
是一种常见的方法。 这比什么都没有好,但是由于其他对象(更不用说子类)能够访问self
的价值,并且可能将其用作context
(导致模糊性),所以它比没有任何东西好,但是与上面提到的方法不同。
另外请记住,这不仅仅是可能导致陷阱的子类, 虽然可以说是一种罕见的模式,但没有什么能够阻止另一个对象注册您的对象以获得新的KVO观察结果。
为了提高可读性,您还可以将其封装在预处理macros中,如下所示:
#define MyKVOContext(A) static void * const A = (void*)&A;
KVO上下文应该是一个指向静态variables的指针,正如这个要点所表明的那样。 通常,我发现自己在做以下事情:
在我的文件的顶部附近ClassName.m
我会ClassName.m
static char ClassNameKVOContext = 0;
当我开始观察targetObject
( TargetClass
一个实例)的aspect
属性时,我会有
[targetObject addObserver:self forKeyPath:PFXKeyTargetClassAspect options://... context:&ClassNameKVOContext];
其中PFXKeyTargetClassAspect是在TargetClass.m
定义的等于@"aspect"
并在TargetClass.h
声明为extern
的NSString *
。 (当然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;
见文档 。