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 senterrororcompletedEvent, 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) calldisposeMethods.
  • However, the official recommendation is to use the clean package (DisposeBag) to manage the subscription lifecycle, typically by adding resources to a global contextDisposeBagInside, it follows the life cycle of the page when the page is destroyedDisposeBagWill be destroyed with it, and at the same timeDisposeBagThe 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 createdObservable<Any>.createMethod has a trailing closure and needs to return an implementationDisposableAn instance of a protocol.
  • Into theDisposables.createLook inside the method
extension Disposables {
    public static func create(with dispose: @escaping () -> Void) -> Cancelable {
        return AnonymousDisposable(disposeAction: dispose)
    }
}
Copy the code
  • Created aAnonymousDisposableObject 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 adisposeMethod,fetchOr(self._isDisposed, 1) == 0This line of code controls that the if statement only goes in once.
  • fetchOrConcrete implementation of the method:AtomicIntIs inheritedNSLockIn the changevalueValue, add a lock to keep the thread safe, then apply or and save the result. Bit operations are much more efficient.
  • Finally the firstself._disposeActionAssign a value to a temporary variableaction, and then emptyself._disposeActionAnd then to performaction(). The reason for doing this is if_disposeActionClosures are a time-consuming operation and can be guaranteed_disposeActionCapable 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:_subscriberHandleIn fact, there is a very important process before this process: subscribe, enter toobservable.subscribemethods
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 firstDisposableObject 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 emitteddisposable.dispose()This confirms what was said earlier: if a sequence is senterrororcompletedEvent, then all internal resources will be released without us having to manually release them.
  • Look at the last line of codereturn Disposables.create( self.asObservable().subscribe(observer), disposable )That’s returned hereDisposableObject is what we call outside manuallydispose.dispose()methodsdisposeObject, or added to the globalDisposeBagThe 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 performdispose()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 toProducerOf the classsubscribemethods
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 destroyerSinkDisposerObject and return, so it was the second argument to create the binary destroyer earlier. Before passing rightRxSwiftIn 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 theself.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 aAnonymousObservableSinkObject and save the one created in the previous stepSinkDisposerObject. inAnonymousObservableSinkFound in the source codeonMethod is executed immediately after completion and error signals are emitteddisposeDestroy, so once our sequence signals complete or error it can’t respond again!
  • performsink.run(self)The method executesparent._subscribeHandler(AnyObserver(self))._subscribeHandlerThe closure is creating the sequence outsideObservable<Any>.createTrailing closure of, so the return value isDisposables. Create {print(" Destroy release ")}.
  • Into thesetSinkAndSubscriptionmethods
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:sinksubscriptionIs the destroyers and returned in the previous stepAnonymousObservableSinkObject,AnonymousObservableSinkIt 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 necessarydispose()Then emptynil
  • Well, when executingdispose.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 performeddispose(), we look atdispose()The two attributes saved during initialization are destroyed and then empty.
  • inRxSwift,sinkStore sequence and observer to establish the response relationship between them, when the sequence and observer bridgesinkIf destroyed, they are disconnected from the response relationship and can no longer receive messages.
  • Attach a picture

conclusion

  • A sequence if senterrororcompletedEvent, 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 todisposeBag, it is indisposeBagWhen an object is destroyed, its contents are destroyed in turn