Skip to content

Commit f2589ec

Browse files
authored
Signal resource management with Lifetime. (#404)
* Use Lifetime to manage Signal disposables. * Clean up a few instances of disposable lifetime association. * Update the changelog for #404. [skip ci]
1 parent 5be24a3 commit f2589ec

File tree

11 files changed

+177
-192
lines changed

11 files changed

+177
-192
lines changed

CHANGELOG.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
11
# master
22
*Please add new entries at the top.*
33

4-
1. `SignalProducer.startWithSignal` now returns the value of the setup closure.
4+
1. `Signal` now uses `Lifetime` for resource management. (#404, kudos to @andersio)
5+
6+
The `Signal` initialzer now accepts a generator closure that is passed with the input `Observer` and the `Lifetime` as its arguments. The original variant accepting a single-argument generator closure is now obselete. This is a source breaking change.
7+
8+
```swift
9+
// New: Add `Disposable`s to the `Lifetime`.
10+
let candies = Signal<U, E> { (observer: Signal<U, E>.Observer, lifetime: Lifetime) in
11+
lifetime += trickOrTreat.observe(observer)
12+
}
13+
14+
// Obsolete: Returning a `Disposable`.
15+
let candies = Signal { (observer: Signal<U, E>.Observer) -> Disposable? in
16+
return trickOrTreat.observe(observer)
17+
}
18+
```
19+
20+
1. `SignalProducer.startWithSignal` now returns the value of the setup closure. (#533, kudos to @Burgestrand)
521

622
# 2.1.0-alpha.2
723
1. Disabled code coverage data to allow app submissions with Xcode 9.0 (see https://github.com/Carthage/Carthage/issues/2056, kudos to @NachoSoto)

Sources/Deprecations+Removals.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ extension AnyDisposable {
1111
extension Signal {
1212
@available(*, unavailable, renamed:"promoteError")
1313
public func promoteErrors<F>(_: F.Type) -> Signal<Value, F> { fatalError() }
14+
15+
@available(*, unavailable, message:"Use the `Signal.init` that accepts a two-argument generator.")
16+
public convenience init(_ generator: (Observer) -> Disposable?) { fatalError() }
1417
}
1518

1619
extension SignalProducer {

Sources/Flatten.swift

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -288,18 +288,12 @@ extension Signal where Value: SignalProducerConvertible, Error == Value.Error {
288288
fileprivate func concurrent(limit: UInt) -> Signal<Value.Value, Error> {
289289
precondition(limit > 0, "The concurrent limit must be greater than zero.")
290290

291-
return Signal<Value.Value, Error> { relayObserver in
292-
let disposable = CompositeDisposable()
293-
let relayDisposable = CompositeDisposable()
294-
295-
disposable += relayDisposable
296-
disposable += self.observeConcurrent(relayObserver, limit, relayDisposable)
297-
298-
return disposable
291+
return Signal<Value.Value, Error> { relayObserver, lifetime in
292+
lifetime += self.observeConcurrent(relayObserver, limit, lifetime)
299293
}
300294
}
301295

302-
fileprivate func observeConcurrent(_ observer: Signal<Value.Value, Error>.Observer, _ limit: UInt, _ disposable: CompositeDisposable) -> Disposable? {
296+
fileprivate func observeConcurrent(_ observer: Signal<Value.Value, Error>.Observer, _ limit: UInt, _ lifetime: Lifetime) -> Disposable? {
303297
let state = Atomic(ConcurrentFlattenState<Value.Value, Error>(limit: limit))
304298

305299
func startNextIfNeeded() {
@@ -308,7 +302,7 @@ extension Signal where Value: SignalProducerConvertible, Error == Value.Error {
308302
let deinitializer = ScopedDisposable(AnyDisposable(producerState.deinitialize))
309303

310304
producer.startWithSignal { signal, inner in
311-
let handle = disposable.add(inner)
305+
let handle = lifetime.observeEnded(inner.dispose)
312306

313307
signal.observe { event in
314308
switch event {
@@ -371,12 +365,10 @@ extension SignalProducer where Value: SignalProducerConvertible, Error == Value.
371365
precondition(limit > 0, "The concurrent limit must be greater than zero.")
372366

373367
return SignalProducer<Value.Value, Error> { relayObserver, lifetime in
374-
self.startWithSignal { signal, signalDisposable in
375-
let disposables = CompositeDisposable()
376-
lifetime.observeEnded(signalDisposable.dispose)
377-
lifetime.observeEnded(disposables.dispose)
368+
self.startWithSignal { signal, interruptHandle in
369+
lifetime.observeEnded(interruptHandle.dispose)
378370

379-
_ = signal.observeConcurrent(relayObserver, limit, disposables)
371+
_ = signal.observeConcurrent(relayObserver, limit, lifetime)
380372
}
381373
}
382374
}
@@ -533,14 +525,10 @@ extension Signal where Value: SignalProducerConvertible, Error == Value.Error {
533525
/// - note: The returned signal completes when `signal` and the latest inner
534526
/// signal have both completed.
535527
fileprivate func switchToLatest() -> Signal<Value.Value, Error> {
536-
return Signal<Value.Value, Error> { observer in
537-
let composite = CompositeDisposable()
528+
return Signal<Value.Value, Error> { observer, lifetime in
538529
let serial = SerialDisposable()
539-
540-
composite += serial
541-
composite += self.observeSwitchToLatest(observer, serial)
542-
543-
return composite
530+
lifetime += serial
531+
lifetime += self.observeSwitchToLatest(observer, serial)
544532
}
545533
}
546534

@@ -658,14 +646,10 @@ extension Signal where Value: SignalProducerConvertible, Error == Value.Error {
658646
///
659647
/// The returned signal completes when `self` and the winning inner signal have both completed.
660648
fileprivate func race() -> Signal<Value.Value, Error> {
661-
return Signal<Value.Value, Error> { observer in
662-
let composite = CompositeDisposable()
649+
return Signal<Value.Value, Error> { observer, lifetime in
663650
let relayDisposable = CompositeDisposable()
664-
665-
composite += relayDisposable
666-
composite += self.observeRace(observer, relayDisposable)
667-
668-
return composite
651+
lifetime += relayDisposable
652+
lifetime += self.observeRace(observer, relayDisposable)
669653
}
670654
}
671655

@@ -905,8 +889,8 @@ extension Signal {
905889
/// - transform: A closure that accepts emitted error and returns a signal
906890
/// producer with a different type of error.
907891
public func flatMapError<F>(_ transform: @escaping (Error) -> SignalProducer<Value, F>) -> Signal<Value, F> {
908-
return Signal<Value, F> { observer in
909-
self.observeFlatMapError(transform, observer, SerialDisposable())
892+
return Signal<Value, F> { observer, lifetime in
893+
lifetime += self.observeFlatMapError(transform, observer, SerialDisposable())
910894
}
911895
}
912896

Sources/FoundationExtensions.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ extension Reactive where Base: NotificationCenter {
3030
/// - note: The signal does not terminate naturally. Observers must be
3131
/// explicitly disposed to avoid leaks.
3232
public func notifications(forName name: Notification.Name?, object: AnyObject? = nil) -> Signal<Notification, NoError> {
33-
return Signal { [base = self.base] observer in
33+
return Signal { [base = self.base] observer, lifetime in
3434
let notificationObserver = base.addObserver(forName: name, object: object, queue: nil) { notification in
3535
observer.send(value: notification)
3636
}
3737

38-
return AnyDisposable {
38+
lifetime.observeEnded {
3939
base.removeObserver(notificationObserver)
4040
}
4141
}

Sources/Lifetime.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ public final class Lifetime {
1111
/// - note: Consider using `Lifetime.observeEnded` if only a closure observer
1212
/// is to be attached.
1313
public var ended: Signal<Never, NoError> {
14-
return Signal { observer in
15-
return disposables += observer.sendCompleted
14+
return Signal { observer, lifetime in
15+
lifetime += (disposables += observer.sendCompleted)
1616
}
1717
}
1818

@@ -63,7 +63,8 @@ public final class Lifetime {
6363
/// if `lifetime` has already ended.
6464
@discardableResult
6565
public static func += (lifetime: Lifetime, disposable: Disposable?) -> Disposable? {
66-
return (disposable?.dispose).flatMap(lifetime.observeEnded)
66+
guard let dispose = disposable?.dispose else { return nil }
67+
return lifetime.observeEnded(dispose)
6768
}
6869
}
6970

0 commit comments

Comments
 (0)