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

Cry*_*tal 40 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];
Run Code Online (Sandbox Code Playgroud)

然后在回调中,这样做:

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context{

NSString *action = (NSString*)context;


if([action isEqualToString:GMAP_ANNOTATION_SELECTED]){
Run Code Online (Sandbox Code Playgroud)

我假设在这种情况下,作者只是创建一个字符串,以便稍后在回调中识别.

然后在iOS 5 Pushing the Limits一书中,我看到他这样做:

[self.target addObserf:self forKeyPath:self.property options:0 context:(__bridge void *)self];
Run Code Online (Sandbox Code Playgroud)

打回来:

if ((__bridge id)context == self) {
}
else {
   [super observeValueForKeyPath .......];
}
Run Code Online (Sandbox Code Playgroud)

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

ipm*_*mcc 96

最重要的是(一般来说)您使用的东西(而不是没有),而且无论你使用是独特的私人在您使用它.

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

例如,如果您仅注册didChange通知,但子类注册了同一对象,则使用该NSKeyValueObservingOptionPrior选项注册了keyPath,这就很重要.如果您没有过滤observeValueForKeyPath:...使用a context(或检查更改字典)的调用,那么当您只希望执行一次时,您的处理程序将执行多次.不难想象这会如何导致问题.

我使用的模式是:

static void * const MyClassKVOContext = (void*)&MyClassKVOContext;
Run Code Online (Sandbox Code Playgroud)

该指针将指向它自己的位置,该位置是唯一的(没有其他静态或全局变量可以有这个地址,也不能有任何堆或堆栈分配的对象曾经有这个地址-这是一个非常强大的,但无可否认不是绝对的,保证),感谢链接器.将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]) {
Run Code Online (Sandbox Code Playgroud)

context声明是a void*,这意味着可以保证它是什么.把它NSString*扔到你正在打开一大盒潜在的坏处.如果有人碰巧有一个注册使用NSString*context参数,当你通过非对象物价值这种方法将崩溃isEqualToString:.指针相等(或替代intptr_tuintptr_t相等)是唯一可与context值一起使用的安全检查.

使用self作为一种context常见的方法.它总比没有好,但具有更弱的统一性和隐私性,因为其他对象(更不用说子类)可以访问它的价值self并且可能将其用作context(引起歧义),这与我上面提到的方法不同.

还要记住,这不仅仅是可能导致陷阱的子类; 虽然它可以说是一种罕见的模式,但没有任何东西阻止另一个对象注册你的对象以获得新的KVO观察结果.

为了提高可读性,您还可以将其包装在预处理器宏中,如:

#define MyKVOContext(A) static void * const A = (void*)&A;
Run Code Online (Sandbox Code Playgroud)

  • 这是一篇写得很好的小文章,我对OP没有接受它感到惊讶. (5认同)
  • KVO将上下文视为整数.它并不关心它"指向"它是什么,所以如上所述,让它指向它本身会给出良好的统一性(即,以后碰巧没有一个对象具有相同的地址.) (4认同)

Nat*_*ler 18

KVO上下文应该是指向静态变量的指针,正如这个要点所示.通常,我发现自己在做以下事情:

在我的文件顶部附近,ClassName.m我将有线

static char ClassNameKVOContext = 0;
Run Code Online (Sandbox Code Playgroud)

当我开始观察我所aspect拥有的targetObject(一个实例TargetClass)上的属性时

[targetObject addObserver:self
               forKeyPath:PFXKeyTargetClassAspect
                  options://...
                  context:&ClassNameKVOContext];
Run Code Online (Sandbox Code Playgroud)

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

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

[targetObject removeObserver:self
                  forKeyPath:PFXKeyTargetClassAspect
                     context:&ClassNameKVOContext];
Run Code Online (Sandbox Code Playgroud)

为了避免在我的实现中有太多的缩进-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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)