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

时间:2012-10-04 03:24:44

标签: objective-c key-value-observing

我想知道在观察属性时应该在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 .......];
}

我想知道是否有传递到上下文指针的标准或最佳实践?

3 个答案:

答案 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_tuintptr_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