JIE WANG
JIE WANG

Reputation: 1934

How NotificationCenter#addObserver(forName:object:queue:using:) works?

I have a notification posted in the main thread, and I want to exec something in the background after I receiving that notification.

So I use this method with a background OperationQueue

func addObserver(forName name: NSNotification.Name?, 
          object obj: Any?, 
           queue: OperationQueue?, 
           using block: @escaping (Notification) -> Void) -> NSObjectProtocol

I thought this should work, but it doesn't.

I have read about the doc, there are something I don't understand about the queue, it says:

queue
The operation queue to which block should be added.

If you pass nil, the block is run synchronously on the posting thread.

so if we pass nil, the block will run synchronously on the posting thread, but what about if we pass a queue, does it still run synchronously on the posting thread?

I have wrote some code to test my thinking,

import Foundation

let queue = OperationQueue()
let testNotification = Notification.Name("testNotification")

NotificationCenter.default.addObserver(forName: testNotification, object: nil, queue: queue) { _ in
    print("receive notification: before sleep.")
    Thread.sleep(forTimeInterval: 2)
    print("receive notification: after sleep.")
}

NotificationCenter.default.post(name: testNotification, object: nil)

print("main thread")
RunLoop.main.run()

the output was:

receive notification: before sleep.
receive notification: after sleep.
main thread

so the block does run synchronously on the posting thread.

My question was, what's the point of this method, and what's the point of the params queue, when should we use it?

Upvotes: 3

Views: 2918

Answers (1)

Popmedic
Popmedic

Reputation: 1871

I added:

import Foundation

let queue = OperationQueue()
let testNotification = Notification.Name("testNotification")

NotificationCenter.default.addObserver(forName: testNotification, object: nil,
                                       queue: queue) { _ in

    if queue == OperationQueue.current { print("I am in my queue") }

    print("receive notification: before sleep.")
    Thread.sleep(forTimeInterval: 2)
    print("receive notification: after sleep.")
}

NotificationCenter.default.post(name: testNotification, object: nil)

print("main thread")

This prints out:

I am in my queue
receive notification: before sleep.
receive notification: after sleep.
main thread

We can tell it is running the using block in the OperationQueue queue.

My understanding is the NotificationCenter.post method will wait until the using block is complete (synchronously) regardless of what queue it runs the using block in. There is a NotificationQueue buffer class that gives the functionality you are looking for.

NotificationCenter.default.addObserver(forName: testNotification, object: nil,
                                       queue: queue) { _ in

    print("receive notification: before sleep.")
    Thread.sleep(forTimeInterval: 2)
    print("receive notification: after sleep.")
}

NotificationQueue.default.enqueue(Notification(name: testNotification),
                                  postingStyle: .whenIdle)

print("main thread")

Prints out:

main thread
receive notification: before sleep.
receive notification: after sleep.

I think the reason for the queue parameter is if using a serial queue for access control, one could pass in the queue to use. For example the UIKit uses the main queue to serialize all screen manipulation.You could do something like

NotificationCenter.default.addObserver(forName: testNotification, object: nil,
                                       queue: OperationQueue.main) { _ in
    // Do a bunch of UI stuff
}

So when you update your model from a background queue, you can post a notification to the controller to update the views, and guarantee the UIKit manipulation is done in the main queue.

Upvotes: 4

Related Questions