44

我想知道当你观察一个属性时你应该在 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];

然后在回调中,这样做:

- (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 .......];
}

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

4

3 回答 3

102

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

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

例如,如果您仅注册didChange通知,但子类注册相同的对象和带有NSKeyValueObservingOptionPrior选项的 keyPath,这将很重要。如果您没有过滤对observeValueForKeyPath:...使用 a 的调用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被声明为 a void*,这意味着这就是可以对它是什么做出的所有保证。通过将它投射到一个NSString*你打开一个潜在的坏的大盒子。如果其他人碰巧有一个使用参数NSString*的注册,那么context当您将非对象值传递给isEqualToString:. 指针相等(或替代intptr_tuintptr_t相等)是唯一可以与context值一起使用的安全检查。

使用selfas acontext是一种常见的方法。总比没有好,但唯一性和隐私性要弱得多,因为与我上面建议的方法不同,其他对象(更不用说子类)可以访问self并可能将其用作context(导致歧义)。

还要记住,在这里可能导致陷阱的不仅仅是子类;尽管可以说这是一种罕见的模式,但没有什么可以阻止另一个对象注册您的对象以进行新的 KVO 观察。

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

#define MyKVOContext(A) static void * const A = (void*)&A;
于 2013-01-04T17:59:28.173 回答
19

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

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

static char ClassNameKVOContext = 0;

当我开始观察(的实例)aspect上的属性时,我将拥有targetObjectTargetClass

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

其中 PFXKeyTargetClassAspectNSString *定义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
        }
    }
}
于 2012-10-17T04:35:18.500 回答
4

我认为更好的方法是按照苹果文档的说法来实现它:

类中唯一命名的静态变量的地址是一个很好的上下文。

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

文档

于 2017-02-22T19:24:52.157 回答