Skip to content

Commit

Permalink
Lifetime observation API. Internal refactoring.
Browse files Browse the repository at this point in the history
  • Loading branch information
andersio committed Jan 14, 2017
1 parent 59c9e1e commit 51c17b2
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 35 deletions.
2 changes: 1 addition & 1 deletion Sources/Action.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public final class Action<Input, Output, Error: Swift.Error> {
lifetime = Lifetime(deinitToken)

// Retain the `property` for the created `Action`.
lifetime.ended.observeCompleted { _ = property }
lifetime.observeEnded { _ = property }

executeClosure = { state, input in execute(state as! State.Value, input) }

Expand Down
80 changes: 78 additions & 2 deletions Sources/Lifetime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import enum Result.NoError
/// Represents the lifetime of an object, and provides a hook to observe when
/// the object deinitializes.
public final class Lifetime {
private enum Backing {
case token(Signal<(), NoError>)
case signal(() -> Signal<(), NoError>, (@escaping () -> Void) -> Disposable?)
}

/// MARK: Type properties and methods

/// A `Lifetime` that has already ended.
Expand All @@ -13,17 +18,45 @@ public final class Lifetime {

/// MARK: Instance properties

private let storage: Backing

/// A signal that sends a `completed` event when the lifetime ends.
public let ended: Signal<(), NoError>
@available(*, deprecated, message:"Use `Lifetime.observeEnded` and `Lifetime.attach` instead.")
public var ended: Signal<(), NoError> {
switch storage {
case let .token(signal): return signal
case let .signal(factory, _): return factory()
}
}

/// MARK: Initializers

/// Initialize a `Lifetime` for the given `Signal`.
///
/// - parameters:
/// - signal: The `Signal`.
internal init<U, E: Swift.Error>(_ signal: Signal<U, E>) {
let factory: () -> Signal<(), NoError> = { [weak signal] in
return signal.map { signal in
return Signal { observer in
return signal.observe(Observer(terminated: observer.sendCompleted))
}
} ?? .empty
}

let observe: (@escaping () -> Void) -> Disposable? = { [weak signal] action in
return signal?.observe(Observer(terminated: action))
}

self.storage = .signal(factory, observe)
}

/// Initialize a `Lifetime` object with the supplied ended signal.
///
/// - parameters:
/// - signal: The ended signal.
private init(ended signal: Signal<(), NoError>) {
ended = signal
storage = .token(signal)
}

/// Initialize a `Lifetime` from a lifetime token, which is expected to be
Expand All @@ -39,6 +72,49 @@ public final class Lifetime {
self.init(ended: token.ended)
}

/// Dispose of the given disposable when `self` ends.
///
/// - parameters:
/// - disposable: The disposable to be disposed of.
///
/// - returns: A disposable that detaches `action` from the lifetime, or `nil`
/// if `lifetime` has already ended.
@discardableResult
public func attach(_ disposable: Disposable?) -> Disposable? {
return disposable.flatMap { observeEnded($0.dispose) }
}

/// Observe the termination of `self`.
///
/// - parameters:
/// - action: The action to be invoked when `self` ends.
///
/// - returns: A disposable that detaches `action` from the lifetime, or `nil`
/// if `lifetime` has already ended.
@discardableResult
public func observeEnded(_ action: @escaping () -> Void) -> Disposable? {
switch storage {
case let .token(signal):
return signal.observe(Observer(terminated: action))

case let .signal(_, observe):
return observe(action)
}
}

/// Attach a `Disposable` to the lifetime.
///
/// - parameters:
/// - lifetime: The lifetime to attach to.
/// - disposable: The disposable to be attached.
///
/// - returns: A disposable that detaches `disposable` from the lifetime, or
/// `nil` if `lifetime` has already ended.
@discardableResult
public static func +=(lifetime: Lifetime, disposable: Disposable?) -> Disposable? {
return lifetime.attach(disposable)
}

/// A token object which completes its signal when it deinitializes.
///
/// It is generally used in conjuncion with `Lifetime` as a private
Expand Down
19 changes: 11 additions & 8 deletions Sources/Observer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,18 @@ public final class Observer<Value, Error: Swift.Error> {
/// An initializer that accepts closures for different event types.
///
/// - parameters:
/// - value: Optional closure executed when a `value` event is observed.
/// - failed: Optional closure that accepts an `Error` parameter when a
/// failed event is observed.
/// - completed: Optional closure executed when a `completed` event is
/// observed.
/// - interruped: Optional closure executed when an `interrupted` event is
/// observed.
/// - value: An optional closure that handles `value` events.
/// - failed: An optional closure that handles a `failed` event.
/// - completed: An optional closure that handles a `completed` event.
/// - interruped: An optional closure that handles an `interrupted` event.
/// - terminated: An optional closure that is invoked whenever a terminal
/// event is received.
public convenience init(
value: ((Value) -> Void)? = nil,
failed: ((Error) -> Void)? = nil,
completed: (() -> Void)? = nil,
interrupted: (() -> Void)? = nil
interrupted: (() -> Void)? = nil,
terminated: (() -> Void)? = nil
) {
self.init { event in
switch event {
Expand All @@ -64,12 +64,15 @@ public final class Observer<Value, Error: Swift.Error> {

case let .failed(error):
failed?(error)
terminated?()

case .completed:
completed?()
terminated?()

case .interrupted:
interrupted?()
terminated?()
}
}
}
Expand Down
4 changes: 1 addition & 3 deletions Sources/Property.swift
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,6 @@ public final class Property<Value>: PropertyProtocol {
///
/// Instances of this class are thread-safe.
public final class MutableProperty<Value>: MutablePropertyProtocol {
private let token: Lifetime.Token
private let observer: Signal<Value, NoError>.Observer
private let atomic: RecursiveAtomic<Value>

Expand Down Expand Up @@ -628,8 +627,7 @@ public final class MutableProperty<Value>: MutablePropertyProtocol {
/// - initialValue: Starting value for the mutable property.
public init(_ initialValue: Value) {
(signal, observer) = Signal.pipe()
token = Lifetime.Token()
lifetime = Lifetime(token)
lifetime = Lifetime(signal)

/// Need a recursive lock around `value` to allow recursive access to
/// `value`. Note that recursive sets will still deadlock because the
Expand Down
7 changes: 6 additions & 1 deletion Sources/Signal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1261,7 +1261,12 @@ extension SignalProtocol {
///
/// - returns: A signal that will deliver events until `lifetime` ends.
public func take(during lifetime: Lifetime) -> Signal<Value, Error> {
return take(until: lifetime.ended)
return Signal { observer in
let disposable = CompositeDisposable()
disposable += self.observe(observer)
disposable += lifetime.observeEnded(observer.sendCompleted)
return disposable
}
}

/// Forward events from `self` until `trigger` sends a `value` or
Expand Down
2 changes: 1 addition & 1 deletion Sources/SignalProducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,7 @@ extension SignalProducerProtocol {
///
/// - returns: A producer that will deliver events until `lifetime` ends.
public func take(during lifetime: Lifetime) -> SignalProducer<Value, Error> {
return take(until: lifetime.ended)
return lift { $0.take(during: lifetime) }
}

/// Forward events from `self` until `trigger` sends a `value` or `completed`
Expand Down
Loading

0 comments on commit 51c17b2

Please sign in to comment.