Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Extension for "NotificationCenter" to observe a notification just once and directly unsubscribe.
extension NotificationCenter {
/// Adds an observer to the given notification center, which fires just once.
///
/// Note:
/// - Same parameters as "addObserver", but with default properties
/// See http://apple.co/2zZIYJB for details.
///
/// Parameters:
/// - name: The name of the notification for which to register the observer
/// - object: The object whose notifications the observer wants to receive
/// - queue: The operation queue to which block should be added.
/// - block: The block to be executed when the notification is received.
///
func observeOnce(forName name: NSNotification.Name?,
object obj: Any? = nil,
queue: OperationQueue? = nil,
using block: @escaping (Notification) -> Swift.Void) {
var observer: NSObjectProtocol?
observer = addObserver(forName: name,
object: obj,
queue: queue) { [weak self] (notification) in
defer {
// Remove observer, so closure will be executed just once
self?.removeObserver(observer!)
}
block(notification)
}
}
}
@fxm90

This comment has been minimized.

Copy link
Owner Author

commented Dec 27, 2017

Testcase

import XCTest

class NotificationCenterObserveOnceTests: XCTestCase {
    var notificationCenter: NotificationCenter!

    override func setUp() {
        super.setUp()

        notificationCenter = NotificationCenter()
    }

    override func tearDown() {
        notificationCenter = nil

        super.tearDown()
    }

    func testNotificationReceived() {
        let expect = expectation(description: "Should receive notification")

        let notificationName = Notification.Name(rawValue: "foobar")
        notificationCenter.observeOnce(forName: notificationName) { (_: Notification)  in
            expect.fulfill()
        }

        notificationCenter.post(name: notificationName, object: nil)
        wait(
            for: [expect],
            timeout: TimeInterval(0.1)
        )
    }

    func testNotificationReceivedJustOnce() {
        let expect = expectation(description: "Should receive notification just once")

        let notificationName = Notification.Name(rawValue: "foobar")

        // Setup a counter, to validate closure is executed just once
        var observeOnceCount = 0
        notificationCenter.observeOnce(forName: notificationName) { (_: Notification)  in
            observeOnceCount += 1
        }

        // Setup a second counter, to validate all notifications have been send
        var addObserverCount = 0
        let observer = notificationCenter.addObserver(forName: notificationName, object: nil, queue: nil) { (_: Notification) in
            addObserverCount += 1
        }

        // Trigger multiple notifications
        let notificationQuantity = 5
        let notificationDispatchQueue = DispatchQueue(label: "Queue sending notifications", qos: .userInitiated)
        notificationDispatchQueue.async {
            for _ in 1 ... notificationQuantity {
                self.notificationCenter.post(name: notificationName, object: nil)
            }
        }

        // Check counter after all notifications have been send
        let checkNotificationOffset: TimeInterval = 0.5
        let when = DispatchTime.now() + checkNotificationOffset
        DispatchQueue.main.asyncAfter(deadline: when) {
            self.notificationCenter.removeObserver(observer)

            // Validate counters
            XCTAssertEqual(observeOnceCount, 1)
            XCTAssertEqual(addObserverCount, notificationQuantity)

            expect.fulfill()
        }

        wait(
            for: [expect],
            timeout: checkNotificationOffset + 0.1
        )
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.