当观察对象获得dealloced
时,如何取消注册观察者?
当观察对象被解除分配时,cocoa绑定如何处理这种情况?
通过使用手动KVO,我必须删除dealloc
对象之前的观察(removeObserver)... Cocoa绑定如何处理这个(停止观察被观察对象的dealloc)?
答案 0 :(得分:5)
2017年更新
正如@GregBrown在评论中指出的那样,2013年的原始答案在2017年不起作用。我假设原始答案在2013年确实有效,因为我的做法是不经测试就回答,但是我不再使用任何代码。
那你怎么在2017年解决这个问题呢?最简单的答案是混合,有些人会发现矛盾,但不一定是在使用积木时。以下是一个快速概念验证,其中包含以下警告:
这不是线程安全的。考虑如果两个或多个线程同时执行代码可能会发生什么。标准技术将解决这个问题。
效率不是考虑因素!例如,您可能希望每个类调用dealloc
一次,并在每个实例关联对象中保留观察者/密钥路径列表。
此代码仅支持自动删除,您无法手动选择删除观察者。你可能希望改变它。
代码:
@implementation AutoRemovedKVO
typedef void (*DeallocImp)(id, SEL);
+ (void)forTarget:(NSObject *)target
addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context
{
// register the observer
[target addObserver:observer forKeyPath:keyPath options:options context:context];
// swizzle dealloc to remove it
Class targetClass = target.class;
SEL deallocSelector = NSSelectorFromString(@"dealloc");
DeallocImp currentDealloc = (DeallocImp)method_getImplementation( class_getInstanceMethod(targetClass, deallocSelector) );
// don't capture target strongly in block or dealloc will never get called!
__unsafe_unretained NSObject *targetPointer = target;
void (^replacementBlock)(id self) = ^(__unsafe_unretained id self)
{
if (self == targetPointer)
[targetPointer removeObserver:observer forKeyPath:keyPath];
currentDealloc(self, deallocSelector);
};
class_replaceMethod(targetClass, deallocSelector, imp_implementationWithBlock(replacementBlock), "v@:");
}
@end
__unsafe_unretained
的两种用途都是为了解决ARC的后果。特别是方法通常保留它们的self
参数,dealloc
方法不保留,并且块遵循相同的retain-as-needed模型。要使用块作为dealloc
的实现,需要重写此行为,这是__unsafe_unretained
正在使用的行为。
要使用上述代码,您只需替换:
[b addObserver:a forKeyPath:keyPath options:options context:NULL];
使用:
[AutoRemovedKVO forTarget:b addObserver:a forKeyPath:keyPath options:options context:NULL];
考虑到上述警告,上述代码将在2017年完成工作(未来几年无法保证!)
原创2013答案
这里概述了如何处理这种情况以及类似的情况。
首先查找关联对象。简而言之,您可以将关联对象附加到任何其他对象(使用objc_setAssociatedObject
),并指定关联对象应保留,只要它附加的对象在周围(使用OBJC_ASSOCIATION_RETAIN
)。
使用关联对象,可以安排在取消分配观察对象时自动删除观察者。设X为观察者,Y为观察对象。
创建一个“取消注册”类,比如Z,它采用(通过init)X& Y及其dealloc
方法执行removeObserver
。
要设置观察,X:
现在当Y被解除分配时,Z将被释放,这将导致调用Z dealloc
并取消注册X的观察。
如果你需要在Y仍处于活动状态时删除对X的观察,你可以通过删除相关对象来执行此操作 - 这样做会触发其dealloc
...
当您想要在解除分配另一个对象时触发某些内容时,可以使用此模式。
HTH
答案 1 :(得分:1)
从KVO指南(强调我的):键值观察addObserver:forKeyPath:options:context:
方法不保持对观察对象,观察对象或上下文的强引用。 您应确保在必要时保持对观察,观察,对象和上下文的强引用。
Cocoa Bindings在bind:
和unbind:
期间管理强引用。
回答您的问题“当观察对象被解除分配时,如何取消注册观察者?”:您不注销观察者,因为观察者应该保持对观察对象的强引用。因此,观察到的对象不会被解除分配。
另一种想到这一点的方法是,您正在观察属性,而不是观察到的对象本身。所以你只关心属性设置为nil。因为您作为观察者维护强引用,所以在观察对象完成观察之前,观察对象将不会被释放。
因此,如果观察对象作为属性被移除,您正在观察另一个对象的 ,那将导致您停止观察它(释放强引用),然后允许被观察对象被释放。您正在观察的属性可能是对象的集合,因此您将观察被删除的对象,导致您停止观察并允许该对象解除分类。
答案 2 :(得分:0)
我尝试过的模式并且看起来很有效:X想要观察Y
1)在Y中声明一个属性
@property (assign) id dealloced;
2)在Y的dealloc
- (void)dealloc {
self.dealloced = self;
}
3)在X中观察Y的keyPath dealloced
[Y addObserver:self forKeyPath:@"dealloced" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL];
4)在X中只处理解除分配的KVO更改并取消注册所有观察
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
...
if ([keyPath isEqual:@"dealloced"]) {
id obj = change[@"new"];
[obj removeObserver:self forKeyPath:@"dealloced"];
// remove other observing as well
}
...
}
在X中,我们甚至不需要持有对Y
的引用