preface

After using RxSwift, KVO is often used, but it does not explore how the underlying implementation, whether it is the same as swift KVO implementation principle, this paper will explore the underlying implementation principle of KVO in RxSwift.

KVO que

First look at the basic use of KVO in Swift, three basic operations

// Add an observer
person.addObserver(self, forKeyPath: "name", options: .new, context: nil)
// The observer responds to the callback
 override func observeValue(forKeyPath keyPath: String? , of object: Any? , change: [NSKeyValueChangeKey : Any]? , context: UnsafeMutableRawPointer?){}// Remove the observer
 deinit {
        person.removeObserver(self, forKeyPath: "name")}Copy the code

RxSwift in the basic use of KVO, one step in place, simple and rough

/ / sequence
        self.person.rx.observeWeakly(String.self."name")
            .subscribe(onNext: { (change) in
                print("ObserveWeakly subscribed to KVO:\(String(describing: change))")
            })
            .disposed(by: disposeBag)
Copy the code

RxSwift generally uses KVO in two ways

  • rx.observe: efficient execution, more use, is aKVOSimple encapsulation of mechanisms that can only listenStrongProperty, or risk crashing
  • rx.observeWeakly: is less efficient because it handles object release, preventing weak references. So it’s generally used inweakProperties, but it worksrx.observerCan also be usedrx.observeWeakly

The source code to explore

Add observer

  • Click on theobserveWeaklySource code in, and continue to traceobserveWeaklyKeyPathFor
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}}Copy the code
  • You can see that one is created hereobservableSequence object, clickobserveWeaklyKeyPathForLet’s see what it looks like. Okayobservable
  • finishWithNilWhenDeallocIt’s a fault-tolerant mechanism where if an object is released, it just sends nil to complete, there’s no need to look at it, click on it and see what the method content is
 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)}}Copy the code
  • There are a couple of important steps here
  • weak var weakTarget: AnyObject? = targetIt’s coming intargetObject set to useweakmodified
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
        // The following code is omitted, mainly a encapsulation fault tolerance processing
}
Copy the code
  • Using theisWeakCheck whether the listener property isweak, by checking if there are any in the attribute valueWTo detect the listening by the breakpointnameProperty is not containedWthe
 private func isWeakProperty(_ properyRuntimeInfo: String) -> Bool {
        return properyRuntimeInfo.range(of: ",W,") != nil
    }
Copy the code

  • You can see that what you’re creating here is aKVOObservableClick to find it in the initialization method to listen for properties and objects, etc., is aKVO preserved
  • There are two protocols implemented here,ObservableTypeGive it the properties of a sequence,KVOObservableProtocolMaking it extend several protocol attributes is the idea of protocol-oriented programming
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
        }
    }
    // The rest of the code is omitted
}
Copy the code

Subscribe and send the response

  • subscribeThe process is basically the same as the RX process we explored before, very simple, directly omit this process to the last stepKVOObservableIn thesubscribemethods
  • It turns out that I’m creating aKVOObserverObserver, and follow a closure, click inside to see
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)
    }
}
Copy the code
  • I found that I saved what I just passedcallback
  • There are two protocols implemented here,_RXKVOObserverandDisposable
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(a) {
        super.dispose()
        self.retainSelf = nil
    }

    deinit{#if TRACE_RESOURCES
            _ = Resources.decrementTotal()
        #endif
    }
}
Copy the code
  • Point into the_RXKVOObserverIt turns out to be an OC classRxSwifttheKVOThe underlying implementation utilizes the OC implementation
  • I saved it hereKVOSome information about
  • self.target addObserverThe most important step is the handover of the observer,The VC observation function is handed over to the current inner class
-(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; // Observer transition - Middle class [self.target addObserver:self forKeyPath:self.keyPath options:options context:nil]; return self; }Copy the code
  • When the outside worldnameWhen the value changes, it comes back to the following method in the current class
  • This is where the external pass in is performedcallbackMethod, and passes the new key-value pair back
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    @synchronized(self) {
        self.callback(change[NSKeyValueChangeNewKey]);
    }
Copy the code
  • thiscallbackIt’s the closure at initialization, and it flows back
  • Here byobserver.onSent the response,valueIt was just deliveredchange[NSKeyValueChangeNewKey]
let observer = KVOObserver(parent: self) { value in
            if value as? NSNull! =nil {
                observer.on(.next(nil))
                return
            }
            observer.on(.next(value as? Element))}Copy the code
  • To the outside worldsubsribeAfter the closure, so you can listen for the change, and the flow is clear
 .subscribe(onNext: { (change) in
                print("ObserveWeakly subscribed to KVO:\(String(describing: change))")})Copy the code
  • So how does KVO destroy the wiretaps?
  • There’s another one up theredisposeIt’s not parsed. It’s calledsuperIn thedisposemethods
override func dispose(a) {
    super.dispose()
    self.retainSelf = nil
}
Copy the code
  • That is_RXKVOObserverIn the destruction ofdisposeMethod, you can see that the observer of KVO is removed here, and the current object is set nil
-(void)dispose {
    [self.target removeObserver:self forKeyPath:self.keyPath context:nil];
    self.target = nil;
    self.retainedTarget = nil;
}
Copy the code
  • So whenever the sequence is destroyed, it’s automatically calleddisposeMethod will destroy our KVO observer

conclusion

RxSwift’s KVO low-level implementation is actually a mediator mode, through the transfer of the observer, to achieve the sequence of response and send effect!