diff --git a/Sources/FoundationExtensions.swift b/Sources/FoundationExtensions.swift index 3a9b3320f..3f27a9651 100644 --- a/Sources/FoundationExtensions.swift +++ b/Sources/FoundationExtensions.swift @@ -12,34 +12,23 @@ import enum Result.NoError extension NotificationCenter: ReactiveExtensionsProvider {} extension Reactive where Base: NotificationCenter { - /// Returns a SignalProducer to observe posting of the specified - /// notification. + /// Returns a Signal to observe posting of the specified notification. /// /// - parameters: /// - name: name of the notification to observe /// - object: an instance which sends the notifications /// - /// - returns: A SignalProducer of notifications posted that match the given - /// criteria. + /// - returns: A Signal of notifications posted that match the given criteria. /// - /// - note: If the `object` is deallocated before starting the producer, it - /// will terminate immediately with an `interrupted` event. - /// Otherwise, the producer will not terminate naturally, so it must - /// be explicitly disposed to avoid leaks. - public func notifications(forName name: Notification.Name?, object: AnyObject? = nil) -> SignalProducer { - // We're weakly capturing an optional reference here, which makes destructuring awkward. - let objectWasNil = (object == nil) - return SignalProducer { [base = self.base, weak object] observer, disposable in - guard object != nil || objectWasNil else { - observer.sendInterrupted() - return - } - + /// - note: The signal does not terminate naturally. Observers must be + /// explicitly disposed to avoid leaks. + public func notifications(forName name: Notification.Name?, object: AnyObject? = nil) -> Signal { + return Signal { [base = self.base] observer in let notificationObserver = base.addObserver(forName: name, object: object, queue: nil) { notification in observer.send(value: notification) } - disposable += { + return ActionDisposable { base.removeObserver(notificationObserver) } } diff --git a/Tests/ReactiveSwiftTests/FoundationExtensionsSpec.swift b/Tests/ReactiveSwiftTests/FoundationExtensionsSpec.swift index 8f338c9ff..20377d73a 100644 --- a/Tests/ReactiveSwiftTests/FoundationExtensionsSpec.swift +++ b/Tests/ReactiveSwiftTests/FoundationExtensionsSpec.swift @@ -23,11 +23,11 @@ class FoundationExtensionsSpec: QuickSpec { describe("NotificationCenter.reactive.notifications") { let center = NotificationCenter.default - it("should send notifications on the producer") { - let producer = center.reactive.notifications(forName: .racFirst) + it("should send notifications on the signal") { + let signal = center.reactive.notifications(forName: .racFirst) var notif: Notification? = nil - let disposable = producer.startWithValues { notif = $0 } + let disposable = signal.observeValues { notif = $0 } center.post(name: .racAnother, object: nil) expect(notif).to(beNil()) @@ -36,26 +36,54 @@ class FoundationExtensionsSpec: QuickSpec { expect(notif?.name) == .racFirst notif = nil - disposable.dispose() + disposable?.dispose() center.post(name: .racFirst, object: nil) expect(notif).to(beNil()) } - it("should send Interrupted when the observed object is freed") { - var observedObject: AnyObject? = NSObject() - let producer = center.reactive.notifications(forName: nil, object: observedObject) - observedObject = nil + it("should be freed if it is not reachable and no observer is attached") { + weak var signal: Signal? + var isDisposed = false - var interrupted = false - let disposable = producer.startWithInterrupted { - interrupted = true - } - expect(interrupted) == true + let disposable: Disposable? = { + let innerSignal = center.reactive.notifications(forName: nil) + .on(disposed: { isDisposed = true }) - disposable.dispose() + signal = innerSignal + return innerSignal.observe { _ in } + }() + + expect(isDisposed) == false + expect(signal).toNot(beNil()) + + disposable?.dispose() + + expect(isDisposed) == true + expect(signal).to(beNil()) } + it("should be not freed if it still has one or more active observers") { + weak var signal: Signal? + var isDisposed = false + + let disposable: Disposable? = { + let innerSignal = center.reactive.notifications(forName: nil) + .on(disposed: { isDisposed = true }) + + signal = innerSignal + innerSignal.observe { _ in } + return innerSignal.observe { _ in } + }() + + expect(isDisposed) == false + expect(signal).toNot(beNil()) + + disposable?.dispose() + + expect(isDisposed) == false + expect(signal).toNot(beNil()) + } } } }