Where to remove observer for NSNotification in Swift?
Asked Answered
F

11

103

Where should I remove the observer for NSNotification in Swift, since viewDidUnload and dealloc() are unavailable?

Footstool answered 24/2, 2015 at 7:8 Comment(1)
nowadays you don't need to manually remove them, unless you're using block-style.Basso
D
80

Use below method which functions same as dealloc.

deinit {
    // Release all resources
    // perform the deinitialization
}

A deinitializer is called immediately before a class instance is deallocated. You write deinitializers with the deinit keyword, similar to how intializers are written with the init keyword. Deinitializers are only available on class types.

Swift Deinitializer

Donaldson answered 24/2, 2015 at 7:10 Comment(8)
As of iOS 9, according to an answer below, observers are automatically removed for you unless you're using block-based ones.Plovdiv
@Kampai deinit method for ViewControllerA will not be called when it will push ViewControllerB.Neubauer
@AnirudhaMahale - No, because ViewControllerA is still in navigation controller's stack. deinit for ViewControllerA will be only called when it is not in navigation controller's stack. For example: Switching to rootViewController (if rootViewController is not ViewControllerA)Donaldson
@Kampai: This won't work as if you are adding observer in view controller. There are high chances that it will be caught under retain cycle and won't call deinit at all. Ideal place to call would func viewDidDisappear(_ animated: Bool)Annihilation
@BhanuBirani: Can you please explain any case that, you are mentioning about "high chances". Well In my experience I didn't face any.Donaldson
@Kampai: This happens all time when we've retain cycles in place. For eg. we've been passing blocks while passing from one controller to another in order to come back via navigation stack and execute something. If we've such setup in place we end with having deinit not getting called. It's more of how we implementation the solution.Annihilation
@BhanuBirani: Check this answer, it will help you - #26921727Donaldson
This answer misses the point of the question, the following answer (below) answers it perfectly: #28690489Pocahontas
M
168

As of iOS 9 (and OS X 10.11), you don't need to remove observers yourself, if you're not using block based observers though. The system will do it for you, since it uses zeroing-weak references for observers, where it can.

And if you are using block based observers, make sure you capture self weakly using [weak self] in the closure's capture list, and remove observer in deinit method. If you don't use weak reference to self, deinit method (and thus removal of that observer) will never be called since Notification Center will hold a strong reference to it indefinitely.

More info can be found at Foundation Release Notes for OS X v10.11 and iOS 9.

If the observer is able to be stored as a zeroing-weak reference the underlying storage will store the observer as a zeroing weak reference, alternatively if the object cannot be stored weakly (i.e. it has a custom retain/release mechanism that would prevent the runtime from being able to store the object weakly) it will store the object as a non-weak zeroing reference. This means that observers are not required to un-register in their deallocation method.

Block based observers via the -[NSNotificationCenter addObserverForName: object: queue: usingBlock] method still need to be un-registered when no longer in use since the system still holds a strong reference to these observers.

Melan answered 31/10, 2016 at 9:58 Comment(4)
I'm curious, does it work same for the delegates? I have seen in iOS8, delegates occupy memory and not retain. I used to write delegate = nil in dealloc() method. Does it work the same from now?Donaldson
As a general rule delegates should be declared as weak references and no other work is needed.Melan
Since you specifically mentioned that it does not work for block based observers: Could you elaborate why? Is there a way around that? e.g. [weak self]Onia
Note if you are using a block based observer, the observer is not self but rather the observer is the return value from the addObserver callColumnist
D
80

Use below method which functions same as dealloc.

deinit {
    // Release all resources
    // perform the deinitialization
}

A deinitializer is called immediately before a class instance is deallocated. You write deinitializers with the deinit keyword, similar to how intializers are written with the init keyword. Deinitializers are only available on class types.

Swift Deinitializer

Donaldson answered 24/2, 2015 at 7:10 Comment(8)
As of iOS 9, according to an answer below, observers are automatically removed for you unless you're using block-based ones.Plovdiv
@Kampai deinit method for ViewControllerA will not be called when it will push ViewControllerB.Neubauer
@AnirudhaMahale - No, because ViewControllerA is still in navigation controller's stack. deinit for ViewControllerA will be only called when it is not in navigation controller's stack. For example: Switching to rootViewController (if rootViewController is not ViewControllerA)Donaldson
@Kampai: This won't work as if you are adding observer in view controller. There are high chances that it will be caught under retain cycle and won't call deinit at all. Ideal place to call would func viewDidDisappear(_ animated: Bool)Annihilation
@BhanuBirani: Can you please explain any case that, you are mentioning about "high chances". Well In my experience I didn't face any.Donaldson
@Kampai: This happens all time when we've retain cycles in place. For eg. we've been passing blocks while passing from one controller to another in order to come back via navigation stack and execute something. If we've such setup in place we end with having deinit not getting called. It's more of how we implementation the solution.Annihilation
@BhanuBirani: Check this answer, it will help you - #26921727Donaldson
This answer misses the point of the question, the following answer (below) answers it perfectly: #28690489Pocahontas
G
76

You can use three methods:

  1. after popViewController, back navigationController or dismissViewControllerAnimated:

     deinit {
         print("Remove NotificationCenter Deinit")
         NSNotificationCenter.defaultCenter().removeObserver(self)
     }
    
  2. viewDidDisappear, remove after it is already the next view controller:

     override func viewDidDisappear(animated: Bool) {
         NSNotificationCenter.defaultCenter().removeObserver(self)
     }
    
  3. viewWillDisappear - before opening the next view:

     override func viewWillDisappear(animated: Bool) {
         NSNotificationCenter.defaultCenter().removeObserver(self)
     }
    

Swift 5.0 syntax:

NotificationCenter.default.removeObserver(self)
Gravettian answered 18/12, 2015 at 12:38 Comment(3)
deinit is the best option here, I guess.Cattalo
As of iOS 9, according to @Nikola Milicevic, observers are automatically removed for you unless you're using block-based ones.Plovdiv
Does removing observers when you leave the controller defeat the purpose of having observers? And does deinit only work when you are moving from one class to another programmatically without using storyboards?Karakul
H
26

In Swift 4.2, this is one of the way you can remove observer

deinit {
    NotificationCenter.default.removeObserver(self, name: Notification.Name.Identifier, object: nil)
}

setup addObserver notification in viewDidLoad class

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(didReceivedItemDetail), name: Notification.Name.Identifier, object: nil)
}
Holmun answered 25/1, 2019 at 3:30 Comment(2)
Be aware that under slow network conditions and certain user activity i.e. navigating away during a busy view action deinit may not be called. I have seen this in tests.Logger
@Logger if your deinit method is not called at end of your view controller life cycle then there is memory issue in that class.Holmun
H
4

Swift provides a deinit method that is called on instances of classes before they are destroyed.

https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Deinitialization.html

Hawkshaw answered 24/2, 2015 at 7:11 Comment(0)
H
4

I also want to point out that you should use this method:

func addObserver(_ observer: Any, selector aSelector: Selector, name aName: NSNotification.Name?, object anObject: Any?)

Instead of

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

The latter will not remove the observer (Ran into this problem recently). The former will remove the observer if you are using iOS9.

Huesman answered 28/2, 2017 at 16:17 Comment(3)
When does the former remove the observer?Daguerre
@Daguerre Check this outHuesman
I think thats because you have a retain cycle in the second method and didn't remove the observer manually in dealloc method.Varhol
H
3

Swift 5

I have a chat application, so whenever I go from my ChatLogViewController to some other viewController and then comeback, I have 1 extra Observer of my keyboard notification. To remove that I remove all the observers when I change my viewController or disappear from my chatLogViewController.

override func viewDidDisappear(_ animated: Bool) {    
    super.viewDidDisappear(animated)

    NotificationCenter.default.removeObserver(self)
}
Harleyharli answered 17/8, 2020 at 8:43 Comment(0)
B
3
deinit {
    //Remove Observer 
    NotificationCenter.default.removeObserver(self)
}
Brash answered 18/10, 2022 at 18:30 Comment(0)
N
2
deinit {
    NotificationCenter.default.removeObserver(self)
}
Nishanishi answered 3/12, 2019 at 21:30 Comment(1)
No longer needed in iOS9 and above as Apple now say "It is no longer necessary for an NSNotificationCenter observer to un-register itself when being deallocated.". Reference : developer.apple.com/library/archive/releasenotes/Foundation/…Elliot
T
0

It is also good if you add your observer in viewWillAppear() and remove them in viewWillDisappear()

Teasley answered 1/2, 2018 at 12:8 Comment(0)
K
-1

In some special cases, You should add your observer in

func viewWillAppear(_ animated: Bool)

Because if you have a popup view controller in your main view controller when it shows up, you don't want remove observers from the Notification Center. If you use

func viewDidDisappear(_ animated: Bool)

you will missed notifications after the popup showed up!

SO

You should reset subscription when

func viewWillAppear(_ animated: Bool)

func triggered.

Kollwitz answered 14/7, 2023 at 12:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.