Skip to content

Commit

Permalink
Lifetime observation API.
Browse files Browse the repository at this point in the history
  • Loading branch information
andersio committed Jan 30, 2017
1 parent a47b859 commit 3b938ba
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 20 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
21 changes: 19 additions & 2 deletions Sources/Lifetime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ public final class Lifetime {

/// MARK: Instance properties

private let _ended: Signal<(), NoError>

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

/// MARK: Initializers

Expand All @@ -29,7 +34,7 @@ public final class Lifetime {
/// - parameters:
/// - signal: The ended signal.
private init(ended signal: Signal<(), NoError>) {
ended = signal
_ended = signal
}

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

/// 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? {
return _ended.observe(Observer(terminated: action))
}

/// 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
7 changes: 6 additions & 1 deletion Sources/Signal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1286,7 +1286,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 @@ -814,7 +814,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
44 changes: 40 additions & 4 deletions Tests/ReactiveSwiftTests/LifetimeSpec.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import Quick
import Nimble
import ReactiveSwift
@testable import ReactiveSwift
import Result

final class LifetimeSpec: QuickSpec {
override func spec() {
describe("Lifetime") {
// FIXME: Remove these test cases when the deprecated `Lifetime.ended`
// is removed.
it("should complete its lifetime ended signal when the it deinitializes") {
let object = MutableReference(TestObject())

Expand All @@ -18,6 +20,8 @@ final class LifetimeSpec: QuickSpec {
expect(isCompleted) == true
}

// FIXME: Remove these test cases when the deprecated `Lifetime.ended`
// is removed.
it("should complete its lifetime ended signal even if the lifetime object is being retained") {
let object = MutableReference(TestObject())
let lifetime = object.value!.lifetime
Expand All @@ -37,13 +41,45 @@ final class LifetimeSpec: QuickSpec {

(lifetime, token) = Lifetime.makeLifetime()

var isCompleted = false
lifetime.ended.observeCompleted { isCompleted = true }
var isEnded = false
lifetime.observeEnded { isEnded = true }

token = Lifetime.Token()
_ = token

expect(isCompleted) == true
expect(isEnded) == true
}

it("should notify its observers when the underlying token deinitializes") {
let object = MutableReference(TestObject())

var isEnded = false

object.value!.lifetime.observeEnded { isEnded = true }
expect(isEnded) == false

object.value = nil
expect(isEnded) == true
}

it("should notify its observers of the deinitialization of the underlying token even if the `Lifetime` object is retained") {
let object = MutableReference(TestObject())
let lifetime = object.value!.lifetime

var isEnded = false

lifetime.observeEnded { isEnded = true }
expect(isEnded) == false

object.value = nil
expect(isEnded) == true
}

it("should notify its observers of its deinitialization if it has already ended") {
var isEnded = false

Lifetime.empty.observeEnded { isEnded = true }
expect(isEnded) == true
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/ReactiveSwiftTests/PropertySpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ class PropertySpec: QuickSpec {
var property = Optional(MutableProperty<Int>(1))

var isEnded = false
property!.lifetime.ended.observeCompleted {
property!.lifetime.observeEnded {
isEnded = true
}

Expand Down
2 changes: 1 addition & 1 deletion Tests/ReactiveSwiftTests/SignalProducerSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,7 @@ class SignalProducerSpec: QuickSpec {

it("doesn't extend the lifetime of the throttle property") {
var completed = false
shouldThrottle.lifetime.ended.observeCompleted { completed = true }
shouldThrottle.lifetime.observeEnded { completed = true }

observer.send(value: 1)
shouldThrottle = nil
Expand Down
2 changes: 1 addition & 1 deletion Tests/ReactiveSwiftTests/SignalSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1616,7 +1616,7 @@ class SignalSpec: QuickSpec {

it("doesn't extend the lifetime of the throttle property") {
var completed = false
shouldThrottle.lifetime.ended.observeCompleted { completed = true }
shouldThrottle.lifetime.observeEnded { completed = true }

observer.send(value: 1)
shouldThrottle = nil
Expand Down

0 comments on commit 3b938ba

Please sign in to comment.