17

I have a few notifications that were created using block / trailing closure syntax which look like this:

NotificationCenter.default.addObserver(forName: .NSManagedObjectContextObjectsDidChange, object: moc, queue: nil) { note in
    // implementation
}

Which I was later removing by name, like this:

NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSManagedObjectContextObjectsDidChange, object: moc)

My Question

Is this adequate? Or do I absolutely need to save the NSObjectProtocol to it's own property and remove that property with the following syntax?

NotificationCenter.default.removeObserver(didChangeNotification)
Dan Beaulieu
  • 19,406
  • 19
  • 101
  • 135
  • 1
    Unrelated to the question at hand, make sure that if your observer closure references `self`, that you use `[weak self]` capture list. You don’t want the Notification Center establishing a strong reference to this object (especially if you try to remove the observer in `deinit`; a Catch-22). – Rob May 22 '19 at 20:48

3 Answers3

16

You absolutely need to store the return value in a property and remove that later on.

From https://developer.apple.com/reference/foundation/nsnotificationcenter/1411723-addobserverforname:

Return Value

An opaque object to act as the observer.

When you call any one of the removeObserver methods, the first parameter is the observer to remove. When you set up a block to respond to a notification, self is not the observer, NSNotificationCenter creates its own observer object behind the scenes and returns it to you.

Note: as of iOS 9, you are no longer required to call removeObserver from dealloc/deinit, as that will happen automatically when the observer goes away. So, if you're only targeting iOS 9, this may all just work, but if you're not retaining the returned observer at all, the notification could be removed before you expect it to be. Better safe than sorry.

Dave Weston
  • 6,527
  • 1
  • 29
  • 44
  • 3
    "self is not the observer" This clears things up and eliminates my follow up question. Which was is `.removeObserver(self)` adequate? It's clearly not adequate if `self` is not the observer. Thanks for the documentation link, I'll take a look now. – Dan Beaulieu Jan 29 '17 at 18:41
  • 2
    Yes, if you look at your `.addObserver` call above, you can see that it doesn't pass `self` anywhere, so the Notification Center has no idea where it came from. – Dave Weston Jan 29 '17 at 18:44
7

To add to @Dave's answer, it looks like documentation isn't always 100% accurate. According to this article by Ole Begemann there is a contradiction in the doc and self-removing magic was not happening as of iOS 11.2 in his test app.

So that the answer is still "Yes, one needs to remove that observer manually" (and yes, self is not the observer, the result of addObserver() method is the observer).

Vitalii
  • 4,267
  • 1
  • 40
  • 45
7

Here an example with code, for how a correct implementation looks like:

Declare the variable that gets returned when you add the observer in your class A (the receiver of the notification or observer):

private var fetchTripsNotification: NSObjectProtocol?

In your init method add yourself as an observer:

init() {
    fetchTripsNotification = NotificationCenter.default.addObserver(forName: .needsToFetchTrips, object: nil, queue: nil) { [weak self] _ in
        guard let `self` = self else {
            return
        }
        self.fetchTrips()
    }
}

In the deinit method of your class, make sure to remove the observer:

deinit { NotificationCenter.default.removeObserver(fetchTripsNotification as Any) }

In your class B (the poster of the notification) trigger the notification like usually:

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