diff --git a/Sources/Action.swift b/Sources/Action.swift index 949ba201a..0faa3d7c2 100644 --- a/Sources/Action.swift +++ b/Sources/Action.swift @@ -162,7 +162,7 @@ public final class Action { /// - returns: A producer that forwards events generated by its started unit of work, /// or emits `ActionError.disabled` if the execution attempt is failed. public func apply(_ input: Input) -> SignalProducer> { - return SignalProducer { observer, disposable in + return SignalProducer { observer, lifetime in let startingState = self.state.modify { state -> Any? in if state.isEnabled { state.isExecuting = true @@ -179,7 +179,7 @@ public final class Action { } self.executeClosure(state, input).startWithSignal { signal, signalDisposable in - disposable += signalDisposable + lifetime.observeEnded(signalDisposable.dispose) signal.observe { event in observer.action(event.mapError(ActionError.producerFailed)) @@ -187,7 +187,7 @@ public final class Action { } } - disposable += { + lifetime.observeEnded { self.state.modify { $0.isExecuting = false } diff --git a/Sources/Deprecations+Removals.swift b/Sources/Deprecations+Removals.swift index 152ec567d..8060bb03d 100644 --- a/Sources/Deprecations+Removals.swift +++ b/Sources/Deprecations+Removals.swift @@ -3,6 +3,27 @@ import Dispatch import enum Result.NoError // MARK: Unavailable methods in ReactiveSwift 2.0. +extension Lifetime { + @available(*, unavailable, renamed:"hasEnded") + public var isDisposed: Bool { fatalError() } + + @discardableResult + @available(*, deprecated, message:"Use `observeEnded(_:)` with a method reference to `dispose()` instead. This method is subject to removal in a future release.") + public func add(_ d: Disposable?) -> Disposable? { + return d.flatMap { observeEnded($0.dispose) } + } + + @discardableResult + @available(*, deprecated, message:"Use `observeEnded(_:)` with a method reference to `dispose()` instead. This operator overload is subject to removal in a future release.") + public static func += (left: Lifetime, right: Disposable?) -> Disposable? { + return right.flatMap { left.observeEnded($0.dispose) } + } + + @discardableResult + @available(*, unavailable, message:"Use `observeEnded(_:)` instead.") + public static func += (left: Lifetime, right: () -> Void) -> Disposable? { fatalError() } +} + extension PropertyProtocol { @available(*, unavailable, renamed:"flatMap(_:_:)") public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> P) -> Property { fatalError() } diff --git a/Sources/Flatten.swift b/Sources/Flatten.swift index 3d90c8211..78c2679c7 100644 --- a/Sources/Flatten.swift +++ b/Sources/Flatten.swift @@ -509,11 +509,13 @@ extension SignalProducer where Value: SignalProducerProtocol, Error == Value.Err fileprivate func concurrent(limit: UInt) -> SignalProducer { precondition(limit > 0, "The concurrent limit must be greater than zero.") - return SignalProducer { relayObserver, disposable in + return SignalProducer { relayObserver, lifetime in self.startWithSignal { signal, signalDisposable in - disposable += signalDisposable + let disposables = CompositeDisposable() + lifetime.observeEnded(signalDisposable.dispose) + lifetime.observeEnded(disposables.dispose) - _ = signal.observeConcurrent(relayObserver, limit, disposable) + _ = signal.observeConcurrent(relayObserver, limit, disposables) } } } @@ -767,13 +769,16 @@ extension SignalProducer where Value: SignalProducerProtocol, Error == Value.Err /// - returns: A signal that forwards values from the latest signal sent on /// `signal`, ignoring values sent on previous inner signal. fileprivate func switchToLatest() -> SignalProducer { - return SignalProducer { observer, disposable in + return SignalProducer { observer, lifetime in let latestInnerDisposable = SerialDisposable() - disposable += latestInnerDisposable + lifetime.observeEnded(latestInnerDisposable.dispose) self.startWithSignal { signal, signalDisposable in - disposable += signalDisposable - disposable += signal.observeSwitchToLatest(observer, latestInnerDisposable) + lifetime.observeEnded(signalDisposable.dispose) + + if let disposable = signal.observeSwitchToLatest(observer, latestInnerDisposable) { + lifetime.observeEnded(disposable.dispose) + } } } } @@ -890,13 +895,16 @@ extension SignalProducer where Value: SignalProducerProtocol, Error == Value.Err /// /// The returned producer completes when `self` and the winning inner producer have both completed. fileprivate func race() -> SignalProducer { - return SignalProducer { observer, disposable in + return SignalProducer { observer, lifetime in let relayDisposable = CompositeDisposable() - disposable += relayDisposable + lifetime.observeEnded(relayDisposable.dispose) self.startWithSignal { signal, signalDisposable in - disposable += signalDisposable - disposable += signal.observeRace(observer, relayDisposable) + lifetime.observeEnded(signalDisposable.dispose) + + if let disposable = signal.observeRace(observer, relayDisposable) { + lifetime.observeEnded(disposable.dispose) + } } } } @@ -1216,9 +1224,9 @@ extension SignalProducer { /// - transform: A closure that accepts emitted error and returns a signal /// producer with a different type of error. public func flatMapError(_ transform: @escaping (Error) -> SignalProducer) -> SignalProducer { - return SignalProducer { observer, disposable in + return SignalProducer { observer, lifetime in let serialDisposable = SerialDisposable() - disposable += serialDisposable + lifetime.observeEnded(serialDisposable.dispose) self.startWithSignal { signal, signalDisposable in serialDisposable.inner = signalDisposable diff --git a/Sources/FoundationExtensions.swift b/Sources/FoundationExtensions.swift index db0877e13..7781a5cd4 100644 --- a/Sources/FoundationExtensions.swift +++ b/Sources/FoundationExtensions.swift @@ -63,7 +63,7 @@ extension Reactive where Base: URLSession { /// side error (i.e. when a response with status code other than /// 200...299 is received). public func data(with request: URLRequest) -> SignalProducer<(Data, URLResponse), AnyError> { - return SignalProducer { [base = self.base] observer, disposable in + return SignalProducer { [base = self.base] observer, lifetime in let task = base.dataTask(with: request) { data, response, error in if let data = data, let response = response { observer.send(value: (data, response)) @@ -73,9 +73,7 @@ extension Reactive where Base: URLSession { } } - disposable += { - task.cancel() - } + lifetime.observeEnded(task.cancel) task.resume() } } diff --git a/Sources/Property.swift b/Sources/Property.swift index 2462f31fc..455cc0192 100644 --- a/Sources/Property.swift +++ b/Sources/Property.swift @@ -518,9 +518,10 @@ public final class Property: PropertyProtocol { /// - values: A producer that will start immediately and send values to /// the property. public convenience init(initial: Value, then values: SignalProducer) { - self.init(unsafeProducer: SignalProducer { observer, disposables in + self.init(unsafeProducer: SignalProducer { observer, lifetime in observer.send(value: initial) - disposables += values.start(Observer(mappingInterruptedToCompleted: observer)) + let disposable = values.start(Observer(mappingInterruptedToCompleted: observer)) + lifetime.observeEnded(disposable.dispose) }) } @@ -630,10 +631,12 @@ public final class MutableProperty: ComposableMutablePropertyProtocol { /// followed by all changes over time, then complete when the property has /// deinitialized. public var producer: SignalProducer { - return SignalProducer { [atomic, signal] producerObserver, producerDisposable in + return SignalProducer { [atomic, signal] observer, lifetime in atomic.withValue { value in - producerObserver.send(value: value) - producerDisposable += signal.observe(Observer(mappingInterruptedToCompleted: producerObserver)) + observer.send(value: value) + if let disposable = signal.observe(Observer(mappingInterruptedToCompleted: observer)) { + lifetime.observeEnded(disposable.dispose) + } } } } diff --git a/Sources/SignalProducer.swift b/Sources/SignalProducer.swift index 1d9194a0f..54018377f 100644 --- a/Sources/SignalProducer.swift +++ b/Sources/SignalProducer.swift @@ -19,7 +19,7 @@ import Result public struct SignalProducer { public typealias ProducedSignal = Signal - private let startHandler: (Signal.Observer, CompositeDisposable) -> Void + private let startHandler: (Signal.Observer, Lifetime) -> Void /// Initializes a `SignalProducer` that will emit the same events as the /// given signal. @@ -30,8 +30,10 @@ public struct SignalProducer { /// - parameters: /// - signal: A signal to observe after starting the producer. public init(_ signal: Signal) { - self.init { observer, disposable in - disposable += signal.observe(observer) + self.init { observer, lifetime in + if let disposable = signal.observe(observer) { + lifetime.observeEnded(disposable.dispose) + } } } @@ -48,7 +50,7 @@ public struct SignalProducer { /// /// - parameters: /// - startHandler: A closure that accepts observer and a disposable. - public init(_ startHandler: @escaping (Signal.Observer, CompositeDisposable) -> Void) { + public init(_ startHandler: @escaping (Signal.Observer, Lifetime) -> Void) { self.startHandler = startHandler } @@ -59,7 +61,7 @@ public struct SignalProducer { /// - value: A value that should be sent by the `Signal` in a `value` /// event. public init(value: Value) { - self.init { observer, disposable in + self.init { observer, lifetime in observer.send(value: value) observer.sendCompleted() } @@ -76,7 +78,7 @@ public struct SignalProducer { /// - action: A action that yields a value to be sent by the `Signal` as /// a `value` event. public init(_ action: @escaping () -> Value) { - self.init { observer, disposable in + self.init { observer, lifetime in observer.send(value: action()) observer.sendCompleted() } @@ -89,7 +91,7 @@ public struct SignalProducer { /// - error: An error that should be sent by the `Signal` in a `failed` /// event. public init(error: Error) { - self.init { observer, disposable in + self.init { observer, lifetime in observer.send(error: error) } } @@ -118,11 +120,11 @@ public struct SignalProducer { /// - values: A sequence of values that a `Signal` will send as separate /// `value` events and then complete. public init(_ values: S) where S.Iterator.Element == Value { - self.init { observer, disposable in + self.init { observer, lifetime in for value in values { observer.send(value: value) - if disposable.isDisposed { + if lifetime.hasEnded { break } } @@ -145,7 +147,7 @@ public struct SignalProducer { /// A producer for a Signal that will immediately complete without sending /// any values. public static var empty: SignalProducer { - return self.init { observer, disposable in + return self.init { observer, lifetime in observer.sendCompleted() } } @@ -181,7 +183,7 @@ public struct SignalProducer { return } - startHandler(observer, producerDisposable) + startHandler(observer, Lifetime(.disposable(producerDisposable))) } } @@ -352,10 +354,9 @@ extension SignalProducer { /// - returns: A signal producer that applies signal's operator to every /// created signal. public func lift(_ transform: @escaping (Signal) -> Signal) -> SignalProducer { - return SignalProducer { observer, outerDisposable in + return SignalProducer { observer, lifetime in self.startWithSignal { signal, innerDisposable in - outerDisposable += innerDisposable - + lifetime.observeEnded(innerDisposable.dispose) transform(signal).observe(observer) } } @@ -386,12 +387,12 @@ extension SignalProducer { /// the operator to generate correct results. private func liftRight(_ transform: @escaping (Signal) -> (Signal) -> Signal) -> (SignalProducer) -> SignalProducer { return { otherProducer in - return SignalProducer { observer, outerDisposable in + return SignalProducer { observer, lifetime in self.startWithSignal { signal, disposable in - outerDisposable.add(disposable) + lifetime.observeEnded(disposable.dispose) otherProducer.startWithSignal { otherSignal, otherDisposable in - outerDisposable += otherDisposable + lifetime.observeEnded(otherDisposable.dispose) transform(signal)(otherSignal).observe(observer) } @@ -406,12 +407,12 @@ extension SignalProducer { /// the operator to generate correct results. fileprivate func liftLeft(_ transform: @escaping (Signal) -> (Signal) -> Signal) -> (SignalProducer) -> SignalProducer { return { otherProducer in - return SignalProducer { observer, outerDisposable in + return SignalProducer { observer, lifetime in otherProducer.startWithSignal { otherSignal, otherDisposable in - outerDisposable += otherDisposable + lifetime.observeEnded(otherDisposable.dispose) self.startWithSignal { signal, disposable in - outerDisposable.add(disposable) + lifetime.observeEnded(disposable.dispose) transform(signal)(otherSignal).observe(observer) } @@ -1326,7 +1327,7 @@ extension SignalProducer { /// `value` event and then complete or `failed` event if `result` /// is a `failure`. public static func attempt(_ operation: @escaping () -> Result) -> SignalProducer { - return SignalProducer { observer, disposable in + return SignalProducer { observer, lifetime in operation().analysis(ifSuccess: { value in observer.send(value: value) observer.sendCompleted() @@ -1468,12 +1469,12 @@ extension SignalProducer { disposed: (() -> Void)? = nil, value: ((Value) -> Void)? = nil ) -> SignalProducer { - return SignalProducer { observer, compositeDisposable in + return SignalProducer { observer, lifetime in starting?() defer { started?() } self.startWithSignal { signal, disposable in - compositeDisposable += disposable + lifetime.observeEnded(disposable.dispose) signal .on( event: event, @@ -1503,13 +1504,15 @@ extension SignalProducer { /// - returns: A producer that will deliver events on given `scheduler` when /// started. public func start(on scheduler: Scheduler) -> SignalProducer { - return SignalProducer { observer, compositeDisposable in - compositeDisposable += scheduler.schedule { + return SignalProducer { observer, lifetime in + let disposable = scheduler.schedule { self.startWithSignal { signal, signalDisposable in - compositeDisposable += signalDisposable + lifetime.observeEnded(signalDisposable.dispose) signal.observe(observer) } } + + if let d = disposable { lifetime.observeEnded(d.dispose) } } } } @@ -1714,9 +1717,9 @@ extension SignalProducer { return producer } - return SignalProducer { observer, disposable in + return SignalProducer { observer, lifetime in let serialDisposable = SerialDisposable() - disposable += serialDisposable + lifetime.observeEnded(serialDisposable.dispose) func iterate(_ current: Int) { self.startWithSignal { signal, signalDisposable in @@ -1817,16 +1820,17 @@ extension SignalProducer { // regard to the most specific rule of overload selection in Swift. internal func _then(_ replacement: SignalProducer) -> SignalProducer { - return SignalProducer { observer, observerDisposable in + return SignalProducer { observer, lifetime in self.startWithSignal { signal, signalDisposable in - observerDisposable += signalDisposable + lifetime.observeEnded(signalDisposable.dispose) signal.observe { event in switch event { case let .failed(error): observer.send(error: error) case .completed: - observerDisposable += replacement.start(observer) + let interruptHandle = replacement.start(observer) + lifetime.observeEnded(interruptHandle.dispose) case .interrupted: observer.sendInterrupted() case .value: @@ -1994,10 +1998,10 @@ extension SignalProducer { } } - return SignalProducer { observer, disposable in + return SignalProducer { observer, lifetime in // Don't dispose of the original producer until all observers // have terminated. - disposable += { _ = lifetimeToken } + lifetime.observeEnded { _ = lifetimeToken } while true { var result: Result.Observer>.Token?, ReplayError>! @@ -2008,7 +2012,7 @@ extension SignalProducer { switch result! { case let .success(token): if let token = token { - disposable += { + lifetime.observeEnded { state.modify { $0.removeObserver(using: token) } @@ -2256,11 +2260,12 @@ extension SignalProducer where Value == Date, Error == NoError { precondition(interval.timeInterval >= 0) precondition(leeway.timeInterval >= 0) - return SignalProducer { observer, compositeDisposable in - compositeDisposable += scheduler.schedule(after: scheduler.currentDate.addingTimeInterval(interval), - interval: interval, - leeway: leeway, - action: { observer.send(value: scheduler.currentDate) }) + return SignalProducer { observer, lifetime in + let disposable = scheduler.schedule(after: scheduler.currentDate.addingTimeInterval(interval), + interval: interval, + leeway: leeway, + action: { observer.send(value: scheduler.currentDate) }) + if let d = disposable { lifetime.observeEnded(d.dispose)} } } } diff --git a/Tests/ReactiveSwiftTests/FlattenSpec.swift b/Tests/ReactiveSwiftTests/FlattenSpec.swift index 011c75d81..09ea94146 100644 --- a/Tests/ReactiveSwiftTests/FlattenSpec.swift +++ b/Tests/ReactiveSwiftTests/FlattenSpec.swift @@ -41,10 +41,8 @@ class FlattenSpec: QuickSpec { beforeEach { disposed = false - pipe.input.send(value: SignalProducer { _, disposable in - disposable += ActionDisposable { - disposed = true - } + pipe.input.send(value: SignalProducer { _, lifetime in + lifetime.observeEnded { disposed = true } }) } @@ -79,10 +77,8 @@ class FlattenSpec: QuickSpec { it("disposes original signal when result signal interrupted") { var disposed = false - let disposable = SignalProducer, NoError> { _, disposable in - disposable += ActionDisposable { - disposed = true - } + let disposable = SignalProducer, NoError> { _, lifetime in + lifetime.observeEnded { disposed = true } } .flatten(flattenStrategy) .start() diff --git a/Tests/ReactiveSwiftTests/SignalProducerSpec.swift b/Tests/ReactiveSwiftTests/SignalProducerSpec.swift index 300edb84d..9284e5740 100644 --- a/Tests/ReactiveSwiftTests/SignalProducerSpec.swift +++ b/Tests/ReactiveSwiftTests/SignalProducerSpec.swift @@ -19,7 +19,7 @@ class SignalProducerSpec: QuickSpec { describe("init") { it("should run the handler once per start()") { var handlerCalledTimes = 0 - let signalProducer = SignalProducer() { observer, disposable in + let signalProducer = SignalProducer() { observer, lifetime in handlerCalledTimes += 1 return @@ -31,13 +31,13 @@ class SignalProducerSpec: QuickSpec { expect(handlerCalledTimes) == 2 } - it("should not release signal observers when given disposable is disposed") { - var disposable: Disposable! + it("should release signal observers when given disposable is disposed") { + var lifetime: Lifetime! - let producer = SignalProducer { observer, innerDisposable in - disposable = innerDisposable + let producer = SignalProducer { observer, innerLifetime in + lifetime = innerLifetime - innerDisposable += { + innerLifetime.observeEnded { // This is necessary to keep the observer long enough to // even test the memory management. observer.send(value: 0) @@ -45,7 +45,11 @@ class SignalProducerSpec: QuickSpec { } weak var objectRetainedByObserver: NSObject? - producer.startWithSignal { signal, _ in + + var disposable: Disposable! + producer.startWithSignal { signal, interruptHandle in + disposable = interruptHandle + let object = NSObject() objectRetainedByObserver = object signal.observeValues { _ in _ = object } @@ -54,23 +58,15 @@ class SignalProducerSpec: QuickSpec { expect(objectRetainedByObserver).toNot(beNil()) disposable.dispose() - - // https://github.com/ReactiveCocoa/ReactiveCocoa/pull/2959 - // - // Before #2959, this would be `nil` as the input observer is not - // retained, and observers would not retain the signal. - // - // After #2959, the object is still retained, since the observation - // keeps the signal alive. - expect(objectRetainedByObserver).toNot(beNil()) + expect(objectRetainedByObserver).to(beNil()) } it("should dispose of added disposables upon completion") { let addedDisposable = SimpleDisposable() var observer: Signal<(), NoError>.Observer! - let producer = SignalProducer<(), NoError>() { incomingObserver, disposable in - disposable += addedDisposable + let producer = SignalProducer<(), NoError>() { incomingObserver, lifetime in + lifetime.observeEnded(addedDisposable.dispose) observer = incomingObserver } @@ -85,8 +81,8 @@ class SignalProducerSpec: QuickSpec { let addedDisposable = SimpleDisposable() var observer: Signal<(), TestError>.Observer! - let producer = SignalProducer<(), TestError>() { incomingObserver, disposable in - disposable += addedDisposable + let producer = SignalProducer<(), TestError>() { incomingObserver, lifetime in + lifetime.observeEnded(addedDisposable.dispose) observer = incomingObserver } @@ -101,8 +97,8 @@ class SignalProducerSpec: QuickSpec { let addedDisposable = SimpleDisposable() var observer: Signal<(), NoError>.Observer! - let producer = SignalProducer<(), NoError>() { incomingObserver, disposable in - disposable += addedDisposable + let producer = SignalProducer<(), NoError>() { incomingObserver, lifetime in + lifetime.observeEnded(addedDisposable.dispose) observer = incomingObserver } @@ -116,8 +112,8 @@ class SignalProducerSpec: QuickSpec { it("should dispose of added disposables upon start() disposal") { let addedDisposable = SimpleDisposable() - let producer = SignalProducer<(), TestError>() { _, disposable in - disposable += addedDisposable + let producer = SignalProducer<(), TestError>() { _, lifetime in + lifetime.observeEnded(addedDisposable.dispose) return } @@ -383,8 +379,8 @@ class SignalProducerSpec: QuickSpec { let addedDisposable = SimpleDisposable() var disposable: Disposable! - let producer = SignalProducer() { _, disposable in - disposable += addedDisposable + let producer = SignalProducer() { _, lifetime in + lifetime.observeEnded(addedDisposable.dispose) return } @@ -479,8 +475,8 @@ class SignalProducerSpec: QuickSpec { let addedDisposable = SimpleDisposable() var observer: Signal.Observer! - let producer = SignalProducer() { incomingObserver, disposable in - disposable += addedDisposable + let producer = SignalProducer() { incomingObserver, lifetime in + lifetime.observeEnded(addedDisposable.dispose) observer = incomingObserver } @@ -495,8 +491,8 @@ class SignalProducerSpec: QuickSpec { let addedDisposable = SimpleDisposable() var observer: Signal.Observer! - let producer = SignalProducer() { incomingObserver, disposable in - disposable += addedDisposable + let producer = SignalProducer() { incomingObserver, lifetime in + lifetime.observeEnded(addedDisposable.dispose) observer = incomingObserver } @@ -510,8 +506,8 @@ class SignalProducerSpec: QuickSpec { it("should dispose of the added disposable if the signal is unretained and unobserved upon exiting the scope") { let addedDisposable = SimpleDisposable() - let producer = SignalProducer { _, disposable in - disposable += addedDisposable + let producer = SignalProducer { _, lifetime in + lifetime.observeEnded(addedDisposable.dispose) } var started = false @@ -776,7 +772,7 @@ class SignalProducerSpec: QuickSpec { it("should start signal producers in order as defined") { var ids = [Int]() let createProducer = { (id: Int) -> SignalProducer in - return SignalProducer { observer, disposable in + return SignalProducer { observer, lifetime in ids.append(id) observer.send(value: id) @@ -813,7 +809,7 @@ class SignalProducerSpec: QuickSpec { it("should start signal producers in order as defined") { var ids = [Int]() let createProducer = { (id: Int) -> SignalProducer in - return SignalProducer { observer, disposable in + return SignalProducer { observer, lifetime in ids.append(id) observer.send(value: id) @@ -1094,8 +1090,8 @@ class SignalProducerSpec: QuickSpec { var (disposed, interrupted) = (false, false) let disposable = baseProducer .flatMapError { (error: TestError) -> SignalProducer in - return SignalProducer { _, disposable in - disposable += ActionDisposable { disposed = true } + return SignalProducer { _, lifetime in + lifetime.observeEnded { disposed = true } } } .startWithInterrupted { interrupted = true } @@ -1638,7 +1634,7 @@ class SignalProducerSpec: QuickSpec { innerDisposable = SimpleDisposable() isInnerInterrupted = false isInnerDisposed = false - let innerProducer = SignalProducer { $1.add(innerDisposable) } + let innerProducer = SignalProducer { $1.observeEnded(innerDisposable.dispose) } .on(interrupted: { isInnerInterrupted = true }, disposed: { isInnerDisposed = true }) interrupted = false @@ -2195,9 +2191,9 @@ class SignalProducerSpec: QuickSpec { describe("observeOn") { it("should immediately cancel upstream producer's work when disposed") { - var upstreamDisposable: Disposable! - let producer = SignalProducer<(), NoError>{ _, innerDisposable in - upstreamDisposable = innerDisposable + var upstreamLifetime: Lifetime! + let producer = SignalProducer<(), NoError>{ _, innerLifetime in + upstreamLifetime = innerLifetime } var downstreamDisposable: Disposable! @@ -2208,10 +2204,10 @@ class SignalProducerSpec: QuickSpec { downstreamDisposable = innerDisposable } - expect(upstreamDisposable.isDisposed) == false + expect(upstreamLifetime.hasEnded) == false downstreamDisposable.dispose() - expect(upstreamDisposable.isDisposed) == true + expect(upstreamLifetime.hasEnded) == true } }