Brief introduction of destroyers
The scavengable resource Disposable is one of the core members of RxSwift, and it is mainly used to clean up resources that are no longer needed. So let’s explore how RxSwift manages the life cycle of these resources.
- In general, if a sequence is sent
error
orcompleted
Event, then all internal resources will be released without us having to manually release them. - However, if you need to release these resources early or unsubscribe from them, you can change the value of the returned purgable resources (
Disposable
) calldispose
Methods. - However, the official recommendation is to use the clean package (
DisposeBag
) to manage the subscription lifecycle, typically by adding resources to a global contextDisposeBag
Inside, it follows the life cycle of the page when the page is destroyedDisposeBag
Will be destroyed with it, and at the same timeDisposeBag
The resources inside will be released one by one.
Var disposeBag = disposeBag () // From the parent ViewController override funcviewDidLoad() {
super.viewDidLoad()
...
usernameValid
.bind(to: passwordOutlet.rx.isEnabled)
.disposed(by: disposeBag)
usernameValid
.bind(to: usernameValidOutlet.rx.isHidden)
.disposed(by: disposeBag)
}
Copy the code
Realization exploration of destroyers
The following code is the normal process of creating a sequence, then subscribing, and then destroying it manually.
let observable = Observable<Any>.create { (observer) -> Disposable in
observer.onNext("Happy Chinese Valentine's Day")
return Disposables.create {
print("Destroyed and released.")}}let dispose = observable.subscribe(onNext: { (message) in
print("A new message :\(message)")
}, onError: { (error) in
print("Error")
}, onCompleted: {
print("Complete") {})print("Destroy callback")}print("Start dispose")
dispose.dispose()
Copy the code
Execution result: there is a new message: Tanabata happiness began to call dispose dispose to release the destruction callbackCopy the code
- The first thing to see is the sequence created
Observable<Any>.create
Method has a trailing closure and needs to return an implementationDisposable
An instance of a protocol. - Into the
Disposables.create
Look inside the method
extension Disposables {
public static func create(with dispose: @escaping () -> Void) -> Cancelable {
return AnonymousDisposable(disposeAction: dispose)
}
}
Copy the code
- Created a
AnonymousDisposable
Object and return it. Obviously, this is an anonymous destroyer, very similar to how you create an anonymous sequence when you create a sequence.
fileprivate final class AnonymousDisposable : DisposeBase, Cancelable {
public typealias DisposeAction = () -> Void
private let _isDisposed = AtomicInt(0)
private var _disposeAction: DisposeAction?
public var isDisposed: Bool {
return isFlagSet(self._isDisposed, 1)
}
fileprivate init(_ disposeAction: @escaping DisposeAction) {
self._disposeAction = disposeAction
super.init()
}
fileprivate init(disposeAction: @escaping DisposeAction) {
self._disposeAction = disposeAction
super.init()
}
fileprivate func dispose() {
if fetchOr(self._isDisposed, 1) == 0 {
if let action = self._disposeAction {
self._disposeAction = nil
action()
}
}
}
}
Copy the code
- Save the closure passed in during initialization
- And then you see that there’s a
dispose
Method,fetchOr(self._isDisposed, 1) == 0
This line of code controls that the if statement only goes in once. fetchOr
Concrete implementation of the method:AtomicInt
Is inheritedNSLock
In the changevalue
Value, add a lock to keep the thread safe, then apply or and save the result. Bit operations are much more efficient.- Finally the first
self._disposeAction
Assign a value to a temporary variableaction
, and then emptyself._disposeAction
And then to performaction()
. The reason for doing this is if_disposeAction
Closures are a time-consuming operation and can be guaranteed_disposeAction
Capable of immediate release.
func fetchOr(_ this: AtomicInt, _ mask: Int32) -> Int32 {
this.lock()
let oldValue = this.value
this.value |= mask
this.unlock()
return oldValue
}
Copy the code
final class AtomicInt: NSLock {
fileprivate var value: Int32
public init(_ value: Int32 = 0) {
self.value = value
}
}
Copy the code
- In the above flow, we are in the sequence of the callback closure:
_subscriberHandle
In fact, there is a very important process before this process: subscribe, enter toobservable.subscribe
methods
public func subscribe(onNext: ((Element) -> Void)? = nil, onError: ((Swift.Error) -> Void)? = nil, onCompleted: (() -> Void)? = nil, onDisposed: (() -> Void)? = nil)
-> Disposable {
let disposable: Disposable
if let disposed = onDisposed {
disposable = Disposables.create(with: disposed)
}
else {
disposable = Disposables.create()
}
let observer = AnonymousObserver<Element> { event in
switch event {
case .next(letvalue): onNext? (value)case .error(let error):
if let onError = onError {
onError(error)
}
else {
Hooks.defaultErrorHandler(callStack, error)
}
disposable.dispose()
case .completed:
onCompleted?()
disposable.dispose()
}
}
return Disposables.create(
self.asObservable().subscribe(observer),
disposable
)
}
Copy the code
- I created one first
Disposable
Object and holds the destruction callback closure, which calls the message back out when the destruction is performed - It is also executed after an error and completion event is emitted
disposable.dispose()
This confirms what was said earlier: if a sequence is senterror
orcompleted
Event, then all internal resources will be released without us having to manually release them. - Look at the last line of code
return Disposables.create( self.asObservable().subscribe(observer), disposable )
That’s returned hereDisposable
Object is what we call outside manuallydispose.dispose()
methodsdispose
Object, or added to the globalDisposeBag
The destroyers of. - Trace into view code
public static func create(_ disposable1: Disposable, _ disposable2: Disposable) -> Cancelable {
return BinaryDisposable(disposable1, disposable2)
}
Copy the code
- A binary destroyer is created
func dispose() {
iffetchOr(self._isDisposed, 1) == 0 { self._disposable1? .dispose() self._disposable2? .dispose() self._disposable1 = nil self._disposable2 = nil } }Copy the code
- When you perform
dispose()
Will destroy the two destroyers separately - Then look at the first argument of the binary destroyer creation:
self.asObservable().subscribe(observer)
What is the return value of Came toProducer
Of the classsubscribe
methods
let disposer = SinkDisposer()
let sinkAndSubscription = self.run(observer, cancel: disposer)
disposer.setSinkAndSubscription(sink: sinkAndSubscription.sink, subscription: sinkAndSubscription.subscription)
return disposer
Copy the code
- Created a sink destroyer
SinkDisposer
Object and return, so it was the second argument to create the binary destroyer earlier. Before passing rightRxSwift
In the analysis of core logic, we know that sink is the bridge connecting sequence and observer. When sink is destroyed, the communication between sequence and observer will not be possible. - Into the
self.run(observer, cancel: disposer)
override func run<Observer: ObserverType>(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element {
let sink = AnonymousObservableSink(observer: observer, cancel: cancel)
let subscription = sink.run(self)
return (sink: sink, subscription: subscription)
}
Copy the code
- Created a
AnonymousObservableSink
Object and save the one created in the previous stepSinkDisposer
Object. inAnonymousObservableSink
Found in the source codeon
Method is executed immediately after completion and error signals are emitteddispose
Destroy, so once our sequence signals complete or error it can’t respond again! - perform
sink.run(self)
The method executesparent._subscribeHandler(AnyObserver(self))
._subscribeHandler
The closure is creating the sequence outsideObservable<Any>.create
Trailing closure of, so the return value isDisposables. Create {print(" Destroy release ")}
. - Into the
setSinkAndSubscription
methods
func setSinkAndSubscription(sink: Disposable, subscription: Disposable) {
self._sink = sink
self._subscription = subscription
let previousState = fetchOr(self._state, DisposeState.sinkAndSubscriptionSet.rawValue)
if(previousState & DisposeState.sinkAndSubscriptionSet.rawValue) ! = 0 { rxFatalError("Sink and subscription were already set")}if(previousState & DisposeState.disposed.rawValue) ! = 0 { sink.dispose() subscription.dispose() self._sink = nil self._subscription = nil } }Copy the code
- Two attributes are saved:
sink
和subscription
Is the destroyers and returned in the previous stepAnonymousObservableSink
Object,AnonymousObservableSink
It holds Sink’s destroyerSinkDisposer
- Determine whether the two properties you just saved need to be destroyed based on a state of the record, and execute if necessary
dispose()
Then emptynil
- Well, when executing
dispose.dispose()
What exactly is destroyed when it is destroyed
func dispose() {
let previousState = fetchOr(self._state, DisposeState.disposed.rawValue)
if(previousState & DisposeState.disposed.rawValue) ! = 0 {return
}
if(previousState & DisposeState.sinkAndSubscriptionSet.rawValue) ! = 0 { sink.dispose() subscription.dispose() self._sink = nil self._subscription = nil } }Copy the code
- Either system destruction or our manual destruction will be performed
dispose()
, we look atdispose()
The two attributes saved during initialization are destroyed and then empty. - in
RxSwift
,sink
Store sequence and observer to establish the response relationship between them, when the sequence and observer bridgesink
If destroyed, they are disconnected from the response relationship and can no longer receive messages. - Attach a picture
conclusion
- A sequence if sent
error
orcompleted
Event, then all internal resources will be released without us having to manually release them. - When destruction is performed, it destroys the response relationship between the sequence and the observer, not the sequence and the observer object itself
- If it’s join to
disposeBag
, it is indisposeBag
When an object is destroyed, its contents are destroyed in turn