美文网首页Swift好文收藏
KVO引发出来的血案

KVO引发出来的血案

作者: 氮化镓加砷 | 来源:发表于2016-05-02 13:37 被阅读1348次

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方法里面手动去监听,

相关文章

  • KVO引发出来的血案

    KVO是IOS中非常常用的一个东西,也是相当好用的。 但是常常在用的时候由于观察者和观察属性的增加,导致我们并不知...

  • iOS土味儿讲义(一)--一个Button引发的血案

    iOS土味儿讲义(一)--一个Button引发的血案 iOS土味儿讲义(一)--一个Button引发的血案

  • Insert on duplicate 死锁分析,和解决方法

    一条Insert on duplicate引发的血案

  • 并发

    1)从现实场景出来,解决生活问题 银行转账事情引发的血案 2)如何解决 1)synchronized放在变量上?【...

  • #引发的血案

    注重细节!今天因为js代码中少了一个“#”耽误了两个小时才找到原因,以后要吸取教训,注重细节!#

  • 跨国谐音引发的血案

    跨国谐音引发的血案 最近,刷爆朋友圈的朝天椒:一个中文词引发的血案——美国教授因言获罪事件,成功勾起了...

  • Android 后台任务执行

    参考:手机休眠引发的“血案”[https://github.com/clarkehe/Android/wiki/%...

  • Java泛型--BeanUtils.copyProperties

    BeanUtils.copyProperties引发的血案 在一次使用BeanUtils.copyProperti...

  • 一张截图引发的血案

    曾经有一个馒头引发的血案,而昨天我见证了一张截图引发的血案。下面,我来大致讲讲到底是一个怎么样的事件。 首...

  • 刘希夷:一首诗引发的血案

    一首诗引发的血案 正是武则天开始掌控大唐帝国的命运的时候,这个以诗歌流芳后世的朝代发生了一件由一首诗引发的血案。 ...

网友评论

    本文标题:KVO引发出来的血案

    本文链接:https://www.haomeiwen.com/subject/rcnkrttx.html