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 aKVO
Simple encapsulation of mechanisms that can only listenStrong
Property, or risk crashingrx.observeWeakly
: is less efficient because it handles object release, preventing weak references. So it’s generally used inweak
Properties, but it worksrx.observer
Can also be usedrx.observeWeakly
The source code to explore
Add observer
- Click on the
observeWeakly
Source 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 here
observable
Sequence object, clickobserveWeaklyKeyPathFor
Let’s see what it looks like. Okayobservable
finishWithNilWhenDealloc
It’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? = target
It’s coming intarget
Object set to useweak
modified
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 the
isWeak
Check whether the listener property isweak
, by checking if there are any in the attribute valueW
To detect the listening by the breakpointname
Property is not containedW
the
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 a
KVOObservable
Click to find it in the initialization method to listen for properties and objects, etc., is aKVO preserved
- There are two protocols implemented here,
ObservableType
Give it the properties of a sequence,KVOObservableProtocol
Making 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
subscribe
The process is basically the same as the RX process we explored before, very simple, directly omit this process to the last stepKVOObservable
In thesubscribe
methods- It turns out that I’m creating a
KVOObserver
Observer, 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 passed
callback
- There are two protocols implemented here,
_RXKVOObserver
andDisposable
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
_RXKVOObserver
It turns out to be an OC classRxSwift
theKVO
The underlying implementation utilizes the OC implementation - I saved it here
KVO
Some information about self.target addObserver
The 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 world
name
When the value changes, it comes back to the following method in the current class - This is where the external pass in is performed
callback
Method, 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
- this
callback
It’s the closure at initialization, and it flows back - Here by
observer.on
Sent the response,value
It 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 world
subsribe
After 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 there
dispose
It’s not parsed. It’s calledsuper
In thedispose
methods
override func dispose(a) {
super.dispose()
self.retainSelf = nil
}
Copy the code
- That is
_RXKVOObserver
In the destruction ofdispose
Method, 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 called
dispose
Method 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!