381 lines
9.7 KiB
Swift
381 lines
9.7 KiB
Swift
//
|
|
// Disposable.swift
|
|
// ReactiveSwift
|
|
//
|
|
// Created by Justin Spahr-Summers on 2014-06-02.
|
|
// Copyright (c) 2014 GitHub. All rights reserved.
|
|
//
|
|
|
|
/// Represents something that can be “disposed”, usually associated with freeing
|
|
/// resources or canceling work.
|
|
public protocol Disposable: AnyObject {
|
|
/// Whether this disposable has been disposed already.
|
|
var isDisposed: Bool { get }
|
|
|
|
/// Disposing of the resources represented by `self`. If `self` has already
|
|
/// been disposed of, it does nothing.
|
|
///
|
|
/// - note: Implementations must issue a memory barrier.
|
|
func dispose()
|
|
}
|
|
|
|
/// Represents the state of a disposable.
|
|
private enum DisposableState: Int32 {
|
|
/// The disposable is active.
|
|
case active
|
|
|
|
/// The disposable has been disposed.
|
|
case disposed
|
|
}
|
|
|
|
extension UnsafeAtomicState where State == DisposableState {
|
|
/// Try to transition from `active` to `disposed`.
|
|
///
|
|
/// - returns: `true` if the transition succeeds. `false` otherwise.
|
|
@inline(__always)
|
|
fileprivate func tryDispose() -> Bool {
|
|
return tryTransition(from: .active, to: .disposed)
|
|
}
|
|
}
|
|
|
|
/// A disposable that does not have side effect upon disposal.
|
|
internal final class _SimpleDisposable: Disposable {
|
|
private let state = UnsafeAtomicState<DisposableState>(.active)
|
|
|
|
var isDisposed: Bool {
|
|
return state.is(.disposed)
|
|
}
|
|
|
|
func dispose() {
|
|
_ = state.tryDispose()
|
|
}
|
|
|
|
deinit {
|
|
state.deinitialize()
|
|
}
|
|
}
|
|
|
|
/// A disposable that has already been disposed.
|
|
internal final class NopDisposable: Disposable {
|
|
static let shared = NopDisposable()
|
|
var isDisposed = true
|
|
func dispose() {}
|
|
private init() {}
|
|
}
|
|
|
|
/// A type-erased disposable that forwards operations to an underlying disposable.
|
|
public final class AnyDisposable: Disposable {
|
|
private final class ActionDisposable: Disposable {
|
|
let state: UnsafeAtomicState<DisposableState>
|
|
var action: (() -> Void)?
|
|
|
|
var isDisposed: Bool {
|
|
return state.is(.disposed)
|
|
}
|
|
|
|
init(_ action: (() -> Void)?) {
|
|
self.state = UnsafeAtomicState(.active)
|
|
self.action = action
|
|
}
|
|
|
|
deinit {
|
|
state.deinitialize()
|
|
}
|
|
|
|
func dispose() {
|
|
if state.tryDispose() {
|
|
action?()
|
|
action = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
private let base: Disposable
|
|
|
|
public var isDisposed: Bool {
|
|
return base.isDisposed
|
|
}
|
|
|
|
/// Create a disposable which runs the given action upon disposal.
|
|
///
|
|
/// - parameters:
|
|
/// - action: A closure to run when calling `dispose()`.
|
|
public init(_ action: @escaping () -> Void) {
|
|
base = ActionDisposable(action)
|
|
}
|
|
|
|
/// Create a disposable.
|
|
public init() {
|
|
base = _SimpleDisposable()
|
|
}
|
|
|
|
/// Create a disposable which wraps the given disposable.
|
|
///
|
|
/// - parameters:
|
|
/// - disposable: The disposable to be wrapped.
|
|
public init(_ disposable: Disposable) {
|
|
base = disposable
|
|
}
|
|
|
|
public func dispose() {
|
|
base.dispose()
|
|
}
|
|
}
|
|
|
|
/// A disposable that will dispose of any number of other disposables.
|
|
public final class CompositeDisposable: Disposable {
|
|
private let disposables: Atomic<Bag<Disposable>?>
|
|
private var state: UnsafeAtomicState<DisposableState>
|
|
|
|
public var isDisposed: Bool {
|
|
return state.is(.disposed)
|
|
}
|
|
|
|
/// Initialize a `CompositeDisposable` containing the given sequence of
|
|
/// disposables.
|
|
///
|
|
/// - parameters:
|
|
/// - disposables: A collection of objects conforming to the `Disposable`
|
|
/// protocol
|
|
public init<S: Sequence>(_ disposables: S) where S.Iterator.Element == Disposable {
|
|
let bag = Bag(disposables)
|
|
self.disposables = Atomic(bag)
|
|
self.state = UnsafeAtomicState(.active)
|
|
}
|
|
|
|
/// Initialize a `CompositeDisposable` containing the given sequence of
|
|
/// disposables.
|
|
///
|
|
/// - parameters:
|
|
/// - disposables: A collection of objects conforming to the `Disposable`
|
|
/// protocol
|
|
public convenience init<S: Sequence>(_ disposables: S)
|
|
where S.Iterator.Element == Disposable?
|
|
{
|
|
self.init(disposables.compactMap { $0 })
|
|
}
|
|
|
|
/// Initializes an empty `CompositeDisposable`.
|
|
public convenience init() {
|
|
self.init([Disposable]())
|
|
}
|
|
|
|
public func dispose() {
|
|
if state.tryDispose(), let disposables = disposables.swap(nil) {
|
|
for disposable in disposables {
|
|
disposable.dispose()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Add the given disposable to the composite.
|
|
///
|
|
/// - parameters:
|
|
/// - disposable: A disposable.
|
|
///
|
|
/// - returns: A disposable to remove `disposable` from the composite. `nil` if the
|
|
/// composite has been disposed of, `disposable` has been disposed of, or
|
|
/// `disposable` is `nil`.
|
|
@discardableResult
|
|
public func add(_ disposable: Disposable?) -> Disposable? {
|
|
guard let d = disposable, !d.isDisposed, !isDisposed else {
|
|
disposable?.dispose()
|
|
return nil
|
|
}
|
|
|
|
return disposables.modify { disposables in
|
|
guard let token = disposables?.insert(d) else { return nil }
|
|
|
|
return AnyDisposable { [weak self] in
|
|
self?.disposables.modify {
|
|
$0?.remove(using: token)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Add the given action to the composite.
|
|
///
|
|
/// - parameters:
|
|
/// - action: A closure to be invoked when the composite is disposed of.
|
|
///
|
|
/// - returns: A disposable to remove `disposable` from the composite. `nil` if the
|
|
/// composite has been disposed of, `disposable` has been disposed of, or
|
|
/// `disposable` is `nil`.
|
|
@discardableResult
|
|
public func add(_ action: @escaping () -> Void) -> Disposable? {
|
|
return add(AnyDisposable(action))
|
|
}
|
|
|
|
deinit {
|
|
state.deinitialize()
|
|
}
|
|
|
|
/// Adds the right-hand-side disposable to the left-hand-side
|
|
/// `CompositeDisposable`.
|
|
///
|
|
/// ````
|
|
/// disposable += producer
|
|
/// .filter { ... }
|
|
/// .map { ... }
|
|
/// .start(observer)
|
|
/// ````
|
|
///
|
|
/// - parameters:
|
|
/// - lhs: Disposable to add to.
|
|
/// - rhs: Disposable to add.
|
|
///
|
|
/// - returns: An instance of `DisposableHandle` that can be used to opaquely
|
|
/// remove the disposable later (if desired).
|
|
@discardableResult
|
|
public static func += (lhs: CompositeDisposable, rhs: Disposable?) -> Disposable? {
|
|
return lhs.add(rhs)
|
|
}
|
|
|
|
/// Adds the right-hand-side `ActionDisposable` to the left-hand-side
|
|
/// `CompositeDisposable`.
|
|
///
|
|
/// ````
|
|
/// disposable += { ... }
|
|
/// ````
|
|
///
|
|
/// - parameters:
|
|
/// - lhs: Disposable to add to.
|
|
/// - rhs: Closure to add as a disposable.
|
|
///
|
|
/// - returns: An instance of `DisposableHandle` that can be used to opaquely
|
|
/// remove the disposable later (if desired).
|
|
@discardableResult
|
|
public static func += (lhs: CompositeDisposable, rhs: @escaping () -> Void) -> Disposable? {
|
|
return lhs.add(rhs)
|
|
}
|
|
}
|
|
|
|
/// A disposable that, upon deinitialization, will automatically dispose of
|
|
/// its inner disposable.
|
|
public final class ScopedDisposable<Inner: Disposable>: Disposable {
|
|
/// The disposable which will be disposed when the ScopedDisposable
|
|
/// deinitializes.
|
|
public let inner: Inner
|
|
|
|
public var isDisposed: Bool {
|
|
return inner.isDisposed
|
|
}
|
|
|
|
/// Initialize the receiver to dispose of the argument upon
|
|
/// deinitialization.
|
|
///
|
|
/// - parameters:
|
|
/// - disposable: A disposable to dispose of when deinitializing.
|
|
public init(_ disposable: Inner) {
|
|
inner = disposable
|
|
}
|
|
|
|
deinit {
|
|
dispose()
|
|
}
|
|
|
|
public func dispose() {
|
|
return inner.dispose()
|
|
}
|
|
}
|
|
|
|
extension ScopedDisposable where Inner == AnyDisposable {
|
|
/// Initialize the receiver to dispose of the argument upon
|
|
/// deinitialization.
|
|
///
|
|
/// - parameters:
|
|
/// - disposable: A disposable to dispose of when deinitializing, which
|
|
/// will be wrapped in an `AnyDisposable`.
|
|
public convenience init(_ disposable: Disposable) {
|
|
self.init(Inner(disposable))
|
|
}
|
|
}
|
|
|
|
extension ScopedDisposable where Inner == CompositeDisposable {
|
|
/// Adds the right-hand-side disposable to the left-hand-side
|
|
/// `ScopedDisposable<CompositeDisposable>`.
|
|
///
|
|
/// ````
|
|
/// disposable += { ... }
|
|
/// ````
|
|
///
|
|
/// - parameters:
|
|
/// - lhs: Disposable to add to.
|
|
/// - rhs: Disposable to add.
|
|
///
|
|
/// - returns: An instance of `DisposableHandle` that can be used to opaquely
|
|
/// remove the disposable later (if desired).
|
|
@discardableResult
|
|
public static func += (lhs: ScopedDisposable<CompositeDisposable>, rhs: Disposable?) -> Disposable? {
|
|
return lhs.inner.add(rhs)
|
|
}
|
|
|
|
/// Adds the right-hand-side disposable to the left-hand-side
|
|
/// `ScopedDisposable<CompositeDisposable>`.
|
|
///
|
|
/// ````
|
|
/// disposable += { ... }
|
|
/// ````
|
|
///
|
|
/// - parameters:
|
|
/// - lhs: Disposable to add to.
|
|
/// - rhs: Closure to add as a disposable.
|
|
///
|
|
/// - returns: An instance of `DisposableHandle` that can be used to opaquely
|
|
/// remove the disposable later (if desired).
|
|
@discardableResult
|
|
public static func += (lhs: ScopedDisposable<CompositeDisposable>, rhs: @escaping () -> Void) -> Disposable? {
|
|
return lhs.inner.add(rhs)
|
|
}
|
|
}
|
|
|
|
/// A disposable that disposes of its wrapped disposable, and allows its
|
|
/// wrapped disposable to be replaced.
|
|
public final class SerialDisposable: Disposable {
|
|
private let _inner: Atomic<Disposable?>
|
|
private var state: UnsafeAtomicState<DisposableState>
|
|
|
|
public var isDisposed: Bool {
|
|
return state.is(.disposed)
|
|
}
|
|
|
|
/// The current inner disposable to dispose of.
|
|
///
|
|
/// Whenever this property is set (even to the same value!), the previous
|
|
/// disposable is automatically disposed.
|
|
public var inner: Disposable? {
|
|
get {
|
|
return _inner.value
|
|
}
|
|
|
|
set(disposable) {
|
|
_inner.swap(disposable)?.dispose()
|
|
|
|
if let disposable = disposable, isDisposed {
|
|
disposable.dispose()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Initializes the receiver to dispose of the argument when the
|
|
/// SerialDisposable is disposed.
|
|
///
|
|
/// - parameters:
|
|
/// - disposable: Optional disposable.
|
|
public init(_ disposable: Disposable? = nil) {
|
|
self._inner = Atomic(disposable)
|
|
self.state = UnsafeAtomicState(DisposableState.active)
|
|
}
|
|
|
|
public func dispose() {
|
|
if state.tryDispose() {
|
|
_inner.swap(nil)?.dispose()
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
state.deinitialize()
|
|
}
|
|
}
|