RxSwift(十一) KVO基本使用及实现原理探究

3,380 阅读5分钟

前言

使用RxSwift后,经常会用到KVO,但是并没有去探究底层是如何实现的,是否和swiftKVO实现原理相同,本文将探究RxSwift里的KVO的底层实现原理。

KVO初探

先看看Swift里KVO的基本使用,三步基本操作

//添加观察者
person.addObserver(self, forKeyPath: "name", options: .new, context: nil)
//观察者响应回调
 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    }
//移除观察者
 deinit {
        person.removeObserver(self, forKeyPath: "name")
    }

RxSwift里的KVO的基本使用,一步到位,简单粗暴

// 序列
        self.person.rx.observeWeakly(String.self, "name")
            .subscribe(onNext: { (change) in
                print("observeWeakly订阅到了KVO:\(String(describing: change))")
            })
            .disposed(by: disposeBag)

RxSwift一般用两种方式使用KVO

  • rx.observe: 执行高效,使用的比较多,是一个KVO机制的简单封装,只能监听Strong属性,否则有崩溃的风险
  • rx.observeWeakly: 效率较低一些,因为它要处理对象的释放,防止弱引用。所以它一般使用在weak属性上,但是能用rx.observer的时候,也能使用rx.observeWeakly

源码探索

添加观察者

  • 点击observeWeakly源码进入,并且继续跟踪observeWeaklyKeyPathFor
public func observeWeakly<Element>(_ type: Element.Type, _ keyPath: String, options: KeyValueObservingOptions = [.new, .initial]) -> Observable<Element?> {
        return observeWeaklyKeyPathFor(self.base, keyPath: keyPath, options: options)
            .map { n in
                return n as? Element
            }
    }
  • 可以看到这里创建了一个observable序列对象,点击observeWeaklyKeyPathFor看看创建的是个什么样的observable
  • finishWithNilWhenDealloc是一个容错机制,对象如果被释放就直接发送nil完成,没有观察的必要,点进去看它的方法内容能够明白
 private func observeWeaklyKeyPathFor(_ target: NSObject, keyPath: String, options: KeyValueObservingOptions) -> Observable<AnyObject?> {
        let components = keyPath.components(separatedBy: ".").filter { $0 != "self" }

        let observable = observeWeaklyKeyPathFor(target, keyPathSections: components, options: options)
            .finishWithNilWhenDealloc(target)

        if !options.isDisjoint(with: .initial) {
            return observable
        }
        else {
            return observable
                .skip(1)
        }
    }

  • 这里主要做了几步重要的操作
  • weak var weakTarget: AnyObject? = target 把传入进来的target对象设置为用weak修饰
private func observeWeaklyKeyPathFor(
        _ target: NSObject,
        keyPathSections: [String],
        options: KeyValueObservingOptions
        ) -> Observable<AnyObject?> {

        weak var weakTarget: AnyObject? = target

        let propertyName = keyPathSections[0]
        let remainingPaths = Array(keyPathSections[1..<keyPathSections.count])

        let property = class_getProperty(object_getClass(target), propertyName)
        if property == nil {
            return Observable.error(RxCocoaError.invalidPropertyName(object: target, propertyName: propertyName))
        }
        let propertyAttributes = property_getAttributes(property!)

        // should dealloc hook be in place if week property, or just create strong reference because it doesn't matter
        let isWeak = isWeakProperty(propertyAttributes.map(String.init) ?? "")
        let propertyObservable = KVOObservable(object: target, keyPath: propertyName, options: options.union(.initial), retainTarget: false) as KVOObservable<AnyObject>
        
        // KVO recursion for value changes
        // 后面代码省略,主要是一个封装容错处理
}
  • 使用了isWeak判断这个监听的属性是否是weak, 通过判断属性值里有没有W来判断,通过断点发现监听的name属性是没有包含W
 private func isWeakProperty(_ properyRuntimeInfo: String) -> Bool {
        return properyRuntimeInfo.range(of: ",W,") != nil
    }

  • 看到这里创建的是一个KVOObservable,点进去发现它在初始化方法里保存了需要监听的属性和对象等内容,是一个KVO保存者
  • 这里实现了两个协议,ObservableType让其具有序列的特性,KVOObservableProtocol让其扩展几个协议属性,是面向协议编程的思路
fileprivate final class KVOObservable<Element>
    : ObservableType
    , KVOObservableProtocol {
    typealias Element = Element?

    unowned var target: AnyObject
    var strongTarget: AnyObject?

    var keyPath: String
    var options: KeyValueObservingOptions
    var retainTarget: Bool

    init(object: AnyObject, keyPath: String, options: KeyValueObservingOptions, retainTarget: Bool) {
        self.target = object
        self.keyPath = keyPath
        self.options = options
        self.retainTarget = retainTarget
        if retainTarget {
            self.strongTarget = object
        }
    }
    //其余代码先省略
}

订阅并发送响应

  • subscribe的流程和我们之前探索的RX流程基本一样,非常简单,直接省略这个流程来到上一步最后的KVOObservable里的subscribe方法
  • 发现这里创建的是一个KVOObserver观察者,并跟随一个闭包,点进去看看
fileprivate final class KVOObservable<Element>
    : ObservableType
    , KVOObservableProtocol {
    func subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element? {
        let observer = KVOObserver(parent: self) { value in
            if value as? NSNull != nil {
                observer.on(.next(nil))
                return
            }
            observer.on(.next(value as? Element))
        }

        return Disposables.create(with: observer.dispose)
    }
}
  • 发现这里保存了刚刚传递的callback
  • 这里实现了两个协议,_RXKVOObserverDisposable
fileprivate final class KVOObserver
    : _RXKVOObserver
    , Disposable {
    typealias Callback = (Any?) -> Void

    var retainSelf: KVOObserver?

    init(parent: KVOObservableProtocol, callback: @escaping Callback) {
        #if TRACE_RESOURCES
            _ = Resources.incrementTotal()
        #endif

        super.init(target: parent.target, retainTarget: parent.retainTarget, keyPath: parent.keyPath, options: parent.options.nsOptions, callback: callback)
        self.retainSelf = self
    }

    override func dispose() {
        super.dispose()
        self.retainSelf = nil
    }

    deinit {
        #if TRACE_RESOURCES
            _ = Resources.decrementTotal()
        #endif
    }
}
  • 点进_RXKVOObserver,发现它竟然是一个OC类,说明了RxSwiftKVO底层实现利用了OC实现的
  • 这里保存了KVO的一些相关信息
  • self.target addObserver最重要的一步操作是这里进行了观察者的移交操作,把VC的观察功能移交给了当前的内部类
-(instancetype)initWithTarget:(id)target
                 retainTarget:(BOOL)retainTarget
                      keyPath:(NSString*)keyPath
                      options:(NSKeyValueObservingOptions)options
                     callback:(void (^)(id))callback {
    self = [super init];
    if (!self) return nil;
    
    self.target = target;
    if (retainTarget) {
        self.retainedTarget = target;
    }
    self.keyPath = keyPath;
    self.callback = callback;
    // 观察者移交 - 中间类
    [self.target addObserver:self forKeyPath:self.keyPath options:options context:nil];
    
    return self;
}
  • 当外界的name值变化后,回来到当前类里的下面这个方法
  • 这里执行了外界的传递进来的callback方法,并且传递了新的键值对回去
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    @synchronized(self) {
        self.callback(change[NSKeyValueChangeNewKey]);
    }
  • 这个callback是在初始化时候的闭包,会流转回去
  • 这里通过observer.on发送了响应,value是刚刚传递过来的change[NSKeyValueChangeNewKey]
let observer = KVOObserver(parent: self) { value in
            if value as? NSNull != nil {
                observer.on(.next(nil))
                return
            }
            observer.on(.next(value as? Element))
        }
  • 再流转会外界的subsribe后的闭包,所以这里能够监听到这个变化,整个流程清清楚楚
 .subscribe(onNext: { (change) in
                print("observeWeakly订阅到了KVO:\(String(describing: change))")
            })
  • 那么KVO是怎么销毁监听的呢?
  • 在上面还有一个dispose没解析,这里调用了super里的dispose方法
override func dispose() {
    super.dispose()
    self.retainSelf = nil
}
  • 也就是_RXKVOObserver里的销毁dispose方法,可以看到在这里移除了KVO的观察者,还置nil了当前对象
-(void)dispose {
    [self.target removeObserver:self forKeyPath:self.keyPath context:nil];
    self.target = nil;
    self.retainedTarget = nil;
}
  • 所以只要序列销毁,自动调用dispose方法,就会销毁我们的KVO观察者

总结

RxSwiftKVO底层实现其实是一个中介者模式,通过移交观察者的方式,实现序列的响应和发送效果!