Call to NotificationCenter's removeObserver not removing the observer

I recently started developing my first App in Swift, and am trying to leverage NotificationCenter to communicate changes from a data management class to my ViewControllers. I am adding an observer for a specific notification name in viewWillAppear:animated, making a call to remove the observer in viewWillDisappear:animated, but for some reason the observers are just piling up as I switch from to another ViewController and come back (to the one with observers).


Here is the code I have in viewWillAppear that adds the observer:


override func viewWillAppear(_ animated: Bool)
{
    super.viewWillAppear(animated)
    let nc = NotificationCenter.default
    nc.addObserver(forName: Notification.Name(rawValue: "SyncFinished"), object: nil, queue: nil, using: viewDidReceiveNotification)
}

The code in viewWillDisappear that should be removing it:


override func viewWillDisappear(_ animated: Bool)
{
    super.viewWillDisappear(animated)
      
    let nc = NotificationCenter.default
    nc.removeObserver(self, name: Notification.Name("SyncFinished"), object: nil)
}

And my callback that is fired when a notification is received:


func viewDidReceiveNotification(notification: Notification) -> Void
{
    if (notification.name.rawValue == "SyncFinished")
    {
        print("Notification Received")
    }
}

Can anyone spot what the issue might be? Any help would be appreciated!

try adding in viewWillAppear


print("Notification added")


to see how many notification exist ; if you breakpoint there, you will know where viewWillAppear is called


You can also remove all notifications in viewWillDisappear, so stacked notifications will be removed for the object.

When you add an observer with "addObserver(forName:object:queue:using:)", the actual observer is a private object returned by the function, and that's the observer you need to remove. You need to save the returned value in an instance variable, so that you can pass it to whichever "removeObserver" you decide to use.


The other thing to be careful of is any situations where "viewWillDisappear" isn't called. (There may be some exceptional cases where it is not, I dunno.) So, you should also bulletproof your approach:


— Don't add an oberver in "viewWillAppear" is there is already one. (That saved returned value, if stored in an optional instance variable, is a good way to keep track of whether there is already one.)


— Add a "deinit" to your subclass to remove any observer that wasn't already removed. If your view controller is deallocated with this kind of closure-based observation in place, your app will crash. (An observer added via the old-style selector-based mechanism doesn't have to be removed explicitly, but the new-style ones do.)

Add a "deinit" to your subclass to remove any observer that wasn't already removed.

The problem with this approach is that, if your closure references

self
, then you form a retain loop and thus
deinit
never runs. You can do the weak self dance if you like, but personally I just stick with the selector-based API.

If your view controller is deallocated with this kind of closure-based observation in place, your app will crash.

In my experience you leak, not crash, although that’s not really an improvement )-:

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

>> You can do the weak self dance if you like, but personally I just stick with the selector-based API.


I agree it's a crying shame that the newer closure-based API has that retain loop gotcha. It's like we're being punished for being good developers.


In Swift, it's not so bad, because there's really no "dance" like there is in Obj-C. It's just an "[unowned self]" annotation, and the compiler does the dancing for you. (I believe that "unowned" is sufficient in this case, so there's not even the performance overhead of using "weak".)

I was following the same path and got hung up on objects not going away properly when released. Some of this seemed to be a playground vs non-playground difference, and some of it seemed to be the Objective-C way of doing things. Eventually, I settled on this as an exemplar:


import Foundation

/*
  declare some keys to use when constructing dictionaries
*/
let NameKey = "name"
let OldValueKey = "oldValue"
let NewValueKey = "newValue"

/*
  declare a notification
*/
extension Notification.Name {
    static let myAttributeChanged = Notification.Name("com.example.myAttributeChanged")
}


/*
  a transmitter can be either a struct or a class
*/
class Transmitter {

     init (name : String, attribute : String) {
          print("Transmitter \(name) instantiated")
          self.name = name
          self.attribute = attribute
     }

     var name : String

     var attribute : String {

        didSet {

            print("""
                 Transmitter \(name) changed from \
                 \"\(oldValue)\" to \"\(attribute)\"
                 """)

            NotificationCenter.default.post(

               // the name of the notification to be posted
               name: .myAttributeChanged,

               // the object sending the notification (may be nil)
               object: self,


               // arbitrary stuff to be passed to the receiver
               userInfo: [
                    NameKey:name,
                    OldValueKey:oldValue,
                    NewValueKey:attribute
               ]

            )

        }

    }

}

/*
  a receiver must be a class. There are two reasons:
  1. structs do not have a deinit method so there is no easy place
     to put a removeObserver() and guarantee that it is called.
  2. to work properly with memory management, the closure seems to
     rely on the behaviour of weak references. That, in turn,
     implies reference semantics rather than value semantics.
*/
class Receiver {

     init?() {

          print("Receiver instantiated")

          // take a weak reference to self
          weak var weakSelf = self

          // register to handle the notification
          NotificationCenter.default.addObserver(

               // the name of the notification to be received
               forName: .myAttributeChanged,

               // if non-nil, only notifications sent by that specific
               // object will be received
               object: nil,

               // the dispatch queue to use for the notification. If
               // nil the notification is delivered synchronously
               queue: OperationQueue.main,

               // the closure to invoke when the notification arrives
               using: { notification in
                    weakSelf?.itHappened(note:notification)
               }

          )

    }

    deinit {

        print("Receiver teardown")

        NotificationCenter.default.removeObserver(self)

    }

    func itHappened (note : Notification) {

          print(
               "Notification \(note.name.rawValue) received ",
               terminator : ""
          )

          if let ui = note.userInfo {
               print("""
                    saying \(ui[NameKey] ?? "unspecified") \
                    changed from \"\(ui[OldValueKey] ?? "unspecified")\" \
                    to \"\(ui[NewValueKey] ?? "unspecified")\"
                    """)
          } else {
               print("containing no further information")
          }
     }

}

// instantiate transmitter
var t1 = Transmitter(name: "t1", attribute: "initialised")


// change it - no notification fires

t1.attribute = "changed"

// instantiate receiver
var r = Receiver()

// change transmitter - notification fires
t1.attribute = "changedAgain"

// instantiate second transmitter - init() does not fire notifications
var t2 = Transmitter(name: "t2", attribute: "initialised")

// change transmitter - notification fires
t2.attribute = "changed"

// notifications also fire on KVC changes
let key = \Transmitter.attribute
t2[keyPath:key] = "changedAgain"

// kill the receiver
r = nil

// changes no longer fire notifications
t1.attribute = "changedOnceMore"
t2.attribute = "changedOnceMore"
t2[keyPath:key] = "onceMoreWithFeeling"
Call to NotificationCenter's removeObserver not removing the observer
 
 
Q