我想知道在观察属性时应该在KVO中设置Context指针。我刚开始使用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];
然后在回调中,这样做:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context{
NSString *action = (NSString*)context;
if([action isEqualToString:GMAP_ANNOTATION_SELECTED]){
我假设在这种情况下,作者只是创建一个字符串,以便稍后在回调中识别。
然后在iOS 5 Pushing the Limits一书中,我看到他这样做了:
[self.target addObserf:self forKeyPath:self.property options:0 context:(__bridge void *)self];
回调:
if ((__bridge id)context == self) {
}
else {
[super observeValueForKeyPath .......];
}
我想知道是否有传递到上下文指针的标准或最佳实践?
答案 0 :(得分:94)
重要的是(一般来说)你使用某种东西(而不是什么),而且无论你使用什么,独特和私有你的使用它的。
这里发生的主要缺陷是,当你在某个类中观察,然后有人对你的类进行子类化时,他们会添加对同一个观察对象和同一个keyPath的另一个观察。如果您的原始observeValueForKeyPath:...
实施仅检查了keyPath
,或观察到的object
,或者甚至两者,那可能不足以让您知道它是您的观察被召回。使用context
的值是唯一且隐私的,可以让您更加确定拨打observeValueForKeyPath:...
的电话是您期望的电话。
例如,如果您仅注册了didChange
个通知,但是子类注册了同一个对象,并且使用NSKeyValueObservingOptionPrior
选项注册了keyPath,则这很重要。如果您没有使用observeValueForKeyPath:...
过滤对context
的调用(或检查更改字典),那么当您只希望执行一次时,您的处理程序将执行多次。不难想象这会如何导致问题。
我使用的模式是:
static void * const MyClassKVOContext = (void*)&MyClassKVOContext;
这个指针将指向它自己的位置,并且该位置是唯一的(没有其他静态或全局变量可以拥有此地址,任何堆或堆栈分配的对象也不能拥有此地址 - 它非常强大,虽然不可否认绝对,保证),感谢链接器。 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观测值注册你的物体。
为了提高可读性,您还可以将其包装在预处理器宏中,如:
#define MyKVOContext(A) static void * const A = (void*)&A;
答案 1 :(得分:18)
KVO上下文应该是指向静态变量的指针,如this gist所示。通常,我发现自己在做以下事情:
靠近文件顶部ClassName.m
我会有一行
static char ClassNameKVOContext = 0;
当我开始观察aspect
上的targetObject
属性时(TargetClass
的一个实例)我将拥有
[targetObject addObserver:self
forKeyPath:PFXKeyTargetClassAspect
options://...
context:&ClassNameKVOContext];
其中PFXKeyTargetClassAspect是NSString *
中定义的TargetClass.m
等于@"aspect"
并在extern
中声明为TargetClass.h
。 (当然PFX只是你在项目中使用的前缀的占位符。)这给了我自动完成的优势并保护我免受拼写错误。
当我在aspect
上观察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
}
}
}
答案 2 :(得分:1)
我认为更好的方法是将其作为苹果公司的文件来实现:
类中唯一命名的静态变量的地址构成了良好的上下文。
static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
请参阅documentation。