KVO引发出来的血案
KVO是IOS中非常常用的一个东西,也是相当好用的。
但是常常在用的时候由于观察者和观察属性的增加,导致我们并不知道对象是否已经监听,造成的结果就是重复监听或者移除没有监听的属性,前者可能产生逻辑问题,后者之程序将会crash掉。
其实按道理说,如果我们养成一个良好的KVO习惯的话是不会造成这种问题的,
比如 http://stackoverflow.com/questions/9231896/kvo-how-to-check-if-an-object-is-an-observer 中就有提到,如果
有良好的习惯去写KVO的话,是可以避免这种问题的,但是,难免当属性和对象增多的时候,脑子就不好使了,所以,希望用一个一劳永逸的办法去避免这种事情的发生。
然后找到了这个属性 var observationInfo: UnsafeMutablePointer<Void>
The observationInfo is a pointer that identifies information about all of the observers that are registered with the receiver. The default implementation of this method stores observationInfo in a global dictionary keyed by the receiver’s pointers.
For improved performance, this method and observationInfo can be overridden to store the opaque data pointer in an instance variable. Classes that override this method must not attempt to send Objective-C messages to observationInfo, including retain and release
在OBJC下,把他print出来
id *a = self.apple.observationInfo;
NSLog(@"%@",a);
<NSKeyValueObservationInfo 0x100204d60> (
<NSKeyValueObservance 0x100105630: Observer: 0x100101900, Key path: name, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x100104070>
<NSKeyValueObservance 0x100204470: Observer: 0x100101900, Key path: age, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x100201eb0>
<NSKeyValueObservance 0x100204a50: Observer: 0x100103980, Key path: age, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x100201eb0>
)
observationInfo指针实际上是指向一个NSKeyValueObservationInfo对象,它包含了指定对象上的所有的监听信息。而每条监听信息而是封装在一个NSKeyValueObservance对象中。
看到这,一想太好了,我只要重新写AddObserve方法,每次Add或者remove的时候进行一次判断就可以了,然而NSKeyValueObservationInfo类及NSKeyValueObservance类都是私有类,我们根本没办法拿到这个对象,只能Debug的时候用,所以这条路已经死了。
在这片文章中 http://www.bkjia.com/IOSjc/993206.html#comment 作者dump出了类结构
#import <XXUnknownSuperclass.h>
// Unknown library@class NSArray, NSHashTable;__attribute__((visibility("hidden")))
@interface NSKeyValueObservationInfo : XXUnknownSuperclass
{
@private int _retainCountMinusOne;
NSArray* _observances; unsigned _cachedHash;
BOOL _cachedIsShareable; NSHashTable* _observables;
}
-(id)_initWithObservances:(id*)observances count:(unsigned)count;
-(id)retain;-(oneway void)release;
-(unsigned)retainCount;-(void)dealloc;
-(unsigned)hash;
-(BOOL)isEqual:(id)equal;
-(id)description;@end
@class NSPointerArray, NSKeyValueProperty, NSObject;__attribute__((visibility("hidden")))
@interface NSKeyValueObservance : XXUnknownSuperclass
{
@private int _retainCountMinusOne;
NSObject* _observer;
NSKeyValueProperty* _property;
unsigned _options; void* _context;
NSObject* _originalObservable;
unsigned _cachedUnrotatedHashComponent;
BOOL _cachedIsShareable;
NSPointerArray* _observationInfos;
auto_weak_callback_block _observerWentAwayCallback;
}
-(id)_initWithObserver:(id)observer property:(id)property options:(unsigned)options context:(void*)context originalObservable:(id)observable;
-(id)retain;-(oneway void)release;
-(unsigned)retainCount;
-(void)dealloc;
-(unsigned)hash;
-(BOOL)isEqual:(id)equal;
-(id)description;
-(void)observeValueForKeyPath:(id)keyPath ofObject:(id)object change:(id)change context:(void*)context;
@end
同时也有讲到
The default implementation of this method retrieves the information from a globaldictionary keyed by the receiver’s pointers.
即这个方法的默认实现是以对象的指针作为key,从一个全局的字典中获取信息。由此,我们可以理解为,KVO的信息是存储在一个全局字典中,而不是存储在对象本身。这类似于Notification,所有关于通知的信息都是放在NSNotificationCenter中。
不过,为了提高效率,我们可以重写observationInfo属性的set和get方法,以将这个不透明的数据指针存储到一个实例变量中。但是,在重写时,我们不应该尝试去向这些数据发送一个Objective-C消息,包括retain和release。
最终找到一个解决办法就是,扩展nsobject,自定add和remove方法,通过一个存储数组来保存已经监听的属性和对象。
extension NSObject {
private struct associatedKeys {
static var safe_observersArray = "observers"
}
private var observers: [[String : NSObject]] {
get {
if let observers = objc_getAssociatedObject(self, &associatedKeys.safe_observersArray) as? [[String : NSObject]] {
return observers
} else {
self.observers = [[String : NSObject]]()
return observers
}
} set {
objc_setAssociatedObject(self, &associatedKeys.safe_observersArray, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
public func safe_addObserver(observer: NSObject, forKeyPath keyPath: String) {
let observerInfo = [keyPath : observer]
if observers.indexOf({ $0 == observerInfo }) == nil {
observers.append(observerInfo)
addObserver(observer, forKeyPath: keyPath, options: .New, context: nil)
}
}
public func safe_removeObserver(observer: NSObject, forKeyPath keyPath: String) {
let observerInfo = [keyPath : observer]
if let index = observers.indexOf({ $0 == observerInfo}) {
observers.removeAtIndex(index)
removeObserver(observer, forKeyPath: keyPath)
}
}
}
不过我觉得,如果是需要监听自己的属性的话,应该自己在Set方法里面手动去监听,