长话短说
从这里获取NSObject
类别,并像这样使用它:
if ([observable tdw_hasObserver:observer forKeyPath:@"key.path" context:nil error:nil]) {
[observable removeObserver:observer forKeyPath:@"key.path"];
} else {
}
公共API方法
对于许多没有其他观察者的Foundation类,您可以只检查observationInfo
属性值。当对象没有任何观察者时,该属性返回空指针;否则返回不透明的void *
指针:
if (observable.observationInfo) {
[observable removeObserver:observer forKeyPath:@"key.path"];
} else {
}
然而,这并不适用于大多数UIKit
类和情况,其中您自己使用了多个观察者。
私有 API 方法
如果您不介意使用私有 API,任何对象都可以通过使用 NSObject
的 observationInfo
属性向您提供指向其观察者数据的不透明指针。在幕后,该指针保存一个对象,该对象又保存了所谓的观察数组 - 一种特殊的数据结构,用于描述单个订阅(每次调用 -[NSObject addObserver:forKeyPath:options:context:]
都会在数组中创建一个新实例,即使所有参数都相同)。观察内存布局(至少在撰写本答案时)如下:
@interface NSKeyValueObservance: NSObject {
id _observer;
NSKeyValueProperty *_property;
void *_context;
id originalObservable;
}
这些变量的组合是您要查找的信息。当然,由于它是私有API,您无法可靠地提取此数据,但以下契约自古以来一直在运作:
observationInfo
应该有一个 NSArray
实例变量,其中包含观察数据;
_observer
、_property
和 _context
实例变量名称不变;
_context
和 _observer
可以通过指针地址与所需数据进行比较;
_property
实例变量具有类型为 NSString
的 _keyPath
,用于与所需的键路径进行比较;
有了这个想法,您可以使用类别扩展 NSObject
并实现一个方便的方法,检查是否存在某种组合。
我不介意分享我的实现,但它太多了,无法在一个SO答案中适合。您可以在我的代码片段页面上查看。
请注意,此实现通过名称和(有时)类型执行ivars查找。 它比直接使用ivar偏移量更安全,但仍然非常脆弱。
这里是如何使用它的简单示例:
NSObject *observable = [NSObject new];
NSObject *observer = [NSObject new];
void *observerContext = &observerContext;
[observable addObserver:observer forKeyPath:@"observationInfo" options:NSKeyValueObservingOptionNew context:observerContext];
[observable addObserver:observer forKeyPath:@"observationInfo" options:NSKeyValueObservingOptionNew context:nil];
[observable addObserver:observer forKeyPath:@"observationInfo" options:NSKeyValueObservingOptionNew context:observerContext];
while ([observable tdw_hasObserver:observer forKeyPath:@"observationInfo" context:observerContext error:nil]) {
[observable removeObserver:observer forKeyPath:@"observationInfo" context:observerContext];
}
两句话的文档说明
如果context
和/或keyPath
参数为nil
,则实现将查找具有任何context
和/或keyPath
的订阅。
在任何(预期的)错误情况下,该方法将返回NO
并将错误详细信息写入error
对象(如果提供了相应的指针)。 预期的错误包括私有API中的一些微小更改(但远非全部)。
viewWillAppear:
中调用addObserver:
并相应地在viewWillDisappear:
中调用removeObserver:
就可以正确匹配这些调用。我必须快速修复,所以我将实现 try-catch 解决方案并留下评论以进一步调查原因。 - bneely