Skip to content

Commit

Permalink
notifications as Signal.
Browse files Browse the repository at this point in the history
  • Loading branch information
andersio committed Oct 15, 2016
1 parent dfb2f9a commit 4929a20
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 32 deletions.
25 changes: 7 additions & 18 deletions Sources/FoundationExtensions.swift
Expand Up @@ -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<Notification, NoError> {
// 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<Notification, NoError> {
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)
}
}
Expand Down
56 changes: 42 additions & 14 deletions Tests/ReactiveSwiftTests/FoundationExtensionsSpec.swift
Expand Up @@ -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())
Expand All @@ -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<Notification, NoError>?
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<Notification, NoError>?
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) == true
expect(signal).toNot(beNil())
}
}
}
}

0 comments on commit 4929a20

Please sign in to comment.