我在这里回答我自己的问题,因为我到处都看到了问题中的模式,但没有提到更好的方法的好例子。我已经浪费了几天甚至几周的时间来调试最终发现是由在 KVO 通知传递期间添加和删除观察者引起的问题。在没有保证的情况下,我提出了以下一次性 KVO 通知的实现,它应该避免来自调用-addObserver:...
和-removeObserver:...
内部的问题-observeValueForKeyPath:...
。编码:
NSObject+KVOOneShot.h:
typedef void (^KVOOneShotObserverBlock)(NSString* keyPath, id object, NSDictionary* change, void* context);
@interface NSObject (KVOOneShot)
- (void)addKVOOneShotObserverForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context block: (KVOOneShotObserverBlock)block;
@end
NSObject+KVOOneShot.m:(使用 -fno-objc-arc 编译,这样我们就可以明确保留/释放)
#import "NSObject+KVOOneShot.h"
#import <libkern/OSAtomic.h>
#import <objc/runtime.h>
@interface KVOOneShotObserver : NSObject
- (instancetype)initWithBlock: (KVOOneShotObserverBlock)block;
@end
@implementation NSObject (KVOOneShot)
- (void)addKVOOneShotObserverForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context block: (KVOOneShotObserverBlock)block
{
if (!block || !keyPath)
return;
KVOOneShotObserver* observer = nil;
@try
{
observer = [[KVOOneShotObserver alloc] initWithBlock: block];
// Tie the observer's lifetime to the object it's observing...
objc_setAssociatedObject(self, observer, observer, OBJC_ASSOCIATION_RETAIN);
// Add the observation...
[self addObserver: observer forKeyPath: keyPath options: options context: context];
}
@finally
{
// Make sure we release our hold on the observer, even if something goes wrong above. Probably paranoid of me.
[observer release];
}
}
@end
@implementation KVOOneShotObserver
{
void * volatile _block;
}
- (instancetype)initWithBlock: (KVOOneShotObserverBlock)block
{
if (self = [super init])
{
_block = [block copy];
}
return self;
}
- (void)dealloc
{
[(id)_block release];
[super dealloc];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
KVOOneShotObserverBlock block = (KVOOneShotObserverBlock)_block;
// Get the block atomically, so it can only ever be executed once.
if (block && OSAtomicCompareAndSwapPtrBarrier(block, NULL, &self->_block))
{
// Do it.
@try
{
block(keyPath, object, change, context);
}
@finally
{
// Release it.
[block release];
// Remove the observation whenever...
// Note: This can potentially extend the lifetime of the observer until the observation is removed.
dispatch_async(dispatch_get_main_queue(), ^{
[object removeObserver: self forKeyPath: keyPath context: context];
});
// Don't keep us alive any longer than necessary...
objc_setAssociatedObject(object, self, nil, OBJC_ASSOCIATION_RETAIN);
}
}
}
@end
这里唯一的潜在障碍是dispatch_async
延迟删除可能会通过主运行循环的一次通过来略微延长观察对象的生命周期。这在普通情况下应该没什么大不了的,但值得一提。我最初的想法是删除 中的观察,但我的理解是,当调用ofdealloc
时,我们并不能保证观察到的对象仍然存在。从逻辑上讲,应该是这样,因为观察到的对象将具有唯一的“可见”保留,但是由于我们将此对象传递给我们看不到其实现的 API,因此我们不能完全确定。鉴于此,这感觉是最安全的方式。-dealloc
KVOOneShotObserver