Objective-C: Where to remove observer for NSNotification?
Asked Answered
S

14

104

I have an objective C class. In it, I created a init method and set up a NSNotification in it

//Set up NSNotification
[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(getData)
                                             name:@"Answer Submitted"
                                           object:nil];

Where do I set the [[NSNotificationCenter defaultCenter] removeObserver:self] in this class? I know that for a UIViewController, I can add it into the viewDidUnload method So what needs to be done if I just created an objective c Class?

Solis answered 24/6, 2011 at 14:26 Comment(8)
I put it in the dealloc method.Diplegia
The dealloc method was not automatically created for me when I created the objective c class, so it's ok for me to add it in?Solis
Yes, you can implement -(void)dealloc and then add removeObserser:self in it. This is the most recommended way to put removeObservers:selfVaulting
Is it still ok to put in the dealloc method in iOS 6?Thunderclap
Yes, it is ok to use dealloc in ARC projects as long as you do not call [super dealloc] (you will get a compiler error if you call [super dealloc]). And yes, you can definitely put your removeObserver in dealloc.Dissimilar
Dealloc still exists, but it is not the place for this, your dealloc will never be called because of the notificationcenter references to your objectIowa
@Iowa what do you offer when NotificationCenter references to an object?Anemography
@Shamsiddin That comment of mine is actually wrong. NotificationCentre doesnt keep strong references to objects.Iowa
S
113

The generic answer would be "as soon as you no longer need the notifications". This is obviously not a satisfying answer.

I'd recommend, that you add a call [notificationCenter removeObserver: self] in method dealloc of those classes, which you intend to use as observers, as it is the last chance to unregister an observer cleanly. This will, however, only protect you against crashes due to the notification center notifying dead objects. It cannot protect your code against receiving notifications, when your objects are not yet/no longer in a state in which they can properly handle the notification. For this... See above.

Edit (since the answer seems to draw more comments than I would have thought) All I am trying to say here is: it's really hard to give general advice as to when it's best to remove the observer from the notification center, because that depends:

  • On your use case (Which notifications are observed? When do they get send?)
  • The implementation of the observer (When is it ready to receive notifications? When is it no longer ready?)
  • The intended life-time of the observer (Is it tied to some other object, say, a view or view controller?)
  • ...

So, the best general advice I can come up with: to protect your app. against at least one possible failure, do the removeObserver: dance in dealloc, since that's the last point (in the object's life), where you can do that cleanly. What this does not mean is: "just defer the removal until dealloc is called, and everything will be fine". Instead, remove the observer as soon as the object is no longer ready (or required) to receive notifications. That is the exact right moment. Unfortunately, not knowing the answers to any of the questions mentioned above, I cannot even guess, when that moment would be.

You can always safely removeObserver: an object multiple times (and all but the very first call with a given observer will be nops). So: think about doing it (again) in dealloc just to be sure, but first and foremost: do it at the appropriate moment (which is determined by your use case).

Sammons answered 24/6, 2011 at 14:32 Comment(12)
This is not safe with ARC and could potentially cause a leak. See this discsussion: cocoabuilder.com/archive/cocoa/311831-arc-and-dealloc.htmlFilum
@Filum The article you linked to seems to make my point. What am I missing ?Sammons
I suppose it should be noted in that one should remove observer somewhere else other than dealloc. For example, viewwilldisappearFilum
@Filum -- yes. I hope, that that's the point I am getting across with my answer. Removing the observer in dealloc is only a last line of defence against crashing the app due to a later access to a decallocated object. But the proper place to unregister an observer is usually somewhere else (and often, much earlier in the object's life-cycle). I am not trying to say here "Hey, just do it in dealloc and everything will be fine".Sammons
@Filum "For example, viewWillDisappear" The problem with giving a concrete advise is, that it really depends on what kind of object you register as observer for what kind of event. It may be the right solution to unregister an observer in viewWillDisappear (or viewDidUnload) for UIViewControllers, but that really depends on the use case.Sammons
Yeah I think I misread your answer. I believe I meant to put it on the answer below, sorry about thatFilum
ARC forbits explicit calls to dealloc, eg [super dealloc]. This doesn't work for iOS 6.Southard
This will cause leaks! Dealloc will never be called because the notificationCenter is still holding references to your objectIowa
@mcfedr: no, the NSNotificationCenter does not retain objects added with addObserver:selector:name:object:. It doesn't specify this explicitly in the documentation, but you can infer it from sentences like "Be sure to invoke removeObserver: or removeObserver:name:object: before notificationObserver or any object specified in addObserver:selector:name:object: is deallocated." (or you can run a small test program and verify the behaviour, as I just did to be sure).Sammons
@lekksi -- Unfortunately, the answer you favour will only make sense for UIViewControllers. If we are talking about arbitrary application-defined classes (as the OP explicitly does), you simply cannot give general advice as to when it's best to remove an observer from the notification center. And even in the case of a UIViewController descendant, you do not know in general, whether viewWillAppear:/viewWillDisappear: is really appropriate.Sammons
@Sammons You are right, my wording for the previous comment was not exact enough, sorry about that. :) What I tried to state is that: When working with ViewControllers, iOS7 and ARC (as many of us do nowadays), it might be better to do e.g. as suggested in RickiG's answer than use dealloc.Zwick
which statement is right from below and why !!!! 1) [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(proximityChanged) name: @"UIDeviceProximityStateDidChangeNotification" object:[UIDevice currentDevice]]; 2) [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(proximityChanged) name: UIDeviceProximityStateDidChangeNotification object:[UIDevice currentDevice]];Confounded
E
40

Since iOS 9 it's no longer necessary to remove observers.

In OS X 10.11 and iOS 9.0 NSNotificationCenter and NSDistributedNotificationCenter will no longer send notifications to registered observers that may be deallocated.

https://developer.apple.com/library/mac/releasenotes/Foundation/RN-Foundation/index.html#10_11NotificationCenter

Ecdysiast answered 24/4, 2016 at 11:23 Comment(3)
May be they will not send messages to observers, but I believe they will keep a strong reference to them as I understand. In that case all observers will stay in memory and produce a leak. Сorrect me if I am wrong.Prostration
The linked documentation goes into detail about that. TL;DR: it's a weak reference.Ecdysiast
but of course it's still necessary in case you keep the object referencing them around and just don't want to listen to the notifications anymoreFenella
D
39

Note : This has been tested and working 100% percent

Swift

override func viewWillDisappear(animated: Bool){
    super.viewWillDisappear(animated)
    
    if self.navigationController!.viewControllers.contains(self) == false  //any other hierarchy compare if it contains self or not
    {
        // the view has been removed from the navigation stack or hierarchy, back is probably the cause
        // this will be slow with a large stack however.
        
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
}

PresentedViewController:

override func viewWillDisappear(animated: Bool){
    super.viewWillDisappear(animated)
    
    if self.isBeingDismissed()  //presented view controller
    {
        // remove observer here
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
}

Objective-C

In iOS 6.0 > version , its better to remove observer in viewWillDisappear as viewDidUnload method is deprecated.

 [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];

There is many times its better to remove observer when the view has been removed from the navigation stack or hierarchy.

- (void)viewWillDisappear:(BOOL)animated{
 if (![[self.navigationController viewControllers] containsObject: self]) //any other hierarchy compare if it contains self or not
    {
        // the view has been removed from the navigation stack or hierarchy, back is probably the cause
        // this will be slow with a large stack however.
        
        [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];
    }
}

PresentedViewController:

- (void)viewWillDisappear:(BOOL)animated{
    if ([self isBeingDismissed] == YES) ///presented view controller
    {
        // remove observer here
        [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];
    }
}
Dibru answered 11/4, 2013 at 5:26 Comment(7)
Except a controller may still want notifications when its view is not showing (e.g. to reload a tableView).Thunderclap
@Thunderclap automatically reload/refresh in viewWillAppear:Collegian
@Prince can you explain why viewWillDisapper better then dealloc? so we have add observer to self, so when the self will be dropped from memory it will call dealloc and then all observers will be deleted, is this not a good logic.Wound
Calling removeObserver:self in any of the UIViewController lifecycle events is almost guaranteed to ruin your week. More reading: subjective-objective-c.blogspot.com/2011/04/…Darkness
Putting the removeObserver calls in viewWillDisappear as indicated is definitely the right way to go if the controller is being presented via pushViewController. If you put them in dealloc instead then dealloc will never be called -- in my experience at least...Clynes
how to do the same in swift without type casting [self.navigationController viewControllers] to NSArray ??Metallic
The EDIT suggesting checking the navigation hierarchy for the controller is the perfect solution for @wcochran's point. Very helpfulScorify
F
25

If the observer is added to a view controller, I strongly recommend adding it in viewWillAppear and removing it in viewWillDisappear.

Forzando answered 8/8, 2012 at 15:27 Comment(5)
I'm curious, @RickiG: why do you recommend using viewWillAppear and viewWillDisappear for viewControllers?Purposely
@IsaacOveracker a few reasons: your setup up code (eg. loadView and viewDidLoad) could potentially cause the notifications to be fired and your controller needs to reflect that before it shows. If you do it like this there are a few benefits. At the moment you decided to "leave" the controller you don't care about the notifications and they wont cause you to do logic while the controller is being pushed off screen etc. There are special cases where the controller should receive notifications when it is off-screen I guess you can't do this. But events like that should probably be in your model.Forzando
@IsaacOveracker also with ARC it would be weird to implement dealloc to unsubscribe to notifications.Forzando
Of those what I've tried, with iOS7 this is the best way to register/remove observers when working with UIViewControllers. The only catch is that, in many cases you don't want the observer to be removed when using UINavigationController and pushing another UIViewController to the stack. Solution: You can check if the VC is being popped in viewWillDisappear by calling [self isBeingDismissed].Zwick
Popping the view controller from navigation controller may not cause dealloc to be called immediately. Going back into the view controller may then cause multiple notifications if observer is added in initialization commands.Harriette
P
20
-(void) dealloc {
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      [super dealloc];
}
Polypropylene answered 24/6, 2011 at 14:35 Comment(14)
I'd turn the order of these instructions around... Using self after [super dealloc] makes me nervous... (even if the receiver is unlikely to actually dereference the pointer in any way, well, you never know, how they implemented NSNotificationCenter)Sammons
Hm. it has worked for me. Have you noticed any unusual behavior ?Polypropylene
Nope. This was just a general remark. It just feels like using a malloc ed pointer after the call to free to me...Sammons
Dirk is right -- this is incorrect. [super dealloc] must always be the last statement of your dealloc method. It destroys your object; after it runs, you don't have a valid self anymore. /cc @SammonsHartzog
@Josh : Hey dude. So are you sure about this ? So you are saying that all dealloc statements do not work (or) do not mean anything if I do this way ?Polypropylene
@Legolas: They may work most of the time, but they're not safe.Hartzog
@Josh: Well yeah. They have worked for me so far. Let me try switching things in your way. May be these were the reasons for the memory leaksPolypropylene
If using ARC on iOS 5+, I think [super dealloc] is not needed anymoreAlps
@Alps stronger, it is not allowed under ARC to call [super dealloc]E
So is it still needed to removeObserver in dealloc using ARC ?Hueston
Its ok to put there. You need to basically place in that based on the life cycle of the VC. viewDidDisappear is a good place.Polypropylene
its up to you .. you can remove them in viewWillDisapper or in viewDidDisapper methods.Listel
It is better still to remove observer in dealloc, but without the call to super obviously (it is done automatically by ARC).Rubefaction
This is wrong answer. If dealloc is called, then instance is already unsubscribed (by the system).Fritzsche
L
7

In general I put it into the dealloc method.

Lasko answered 24/6, 2011 at 14:31 Comment(0)
O
6

In swift use deinit because dealloc is unavailable:

deinit {
    ...
}

Swift documentation:

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.

Typically you don’t need to perform manual clean-up when your instances are deallocated. However, when you are working with your own resources, you might need to perform some additional clean-up yourself. For example, if you create a custom class to open a file and write some data to it, you might need to close the file before the class instance is deallocated.

Oarsman answered 22/1, 2015 at 7:45 Comment(3)
Wrong! You should never unsubscribe in deinit because it's useless. When instance is clean from memory iOS unsubscribes it automatically, then it calls deinit, so it's useless.Fritzsche
@AlexanderVolkov Please remember this answer is 5 years old, when swift was brand new. At that time it was the solution but it has changed since.Oarsman
Nope. If deinit is called, then iOS automatically unsubscribed the observer. This is equal in ObjC and in Swift, it's system dependent, not language. Probably I wrong, but then provide a reference to documentation.Fritzsche
R
5

*edit: This advice applies to iOS <= 5 (even there you should be adding in viewWillAppear and removing in viewWillDisappear - however the advice applies if for some reason you've added the observer in viewDidLoad)

If you've added the observer in viewDidLoad you should remove it in both dealloc and viewDidUnload. Otherwise you'll end up adding it twice when viewDidLoad is called after viewDidUnload (this will happen after a memory warning). This isn't necessary in iOS 6 where viewDidUnload is deprecated and won't be called (because views are no longer automatically unloaded).

Rafi answered 25/11, 2012 at 12:17 Comment(1)
Welcome to StackOverflow. Please checkout the MarkDown FAQ (question-mark icon next to the question/answer edit box). Using Markdwon will improve the usability of your answer.Athens
A
5

In my opinion, the following code makes no sense in ARC:

- (void)dealloc
{
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      [super dealloc];
}

In iOS 6, there's also no sense in removing observers in viewDidUnload, because it has been deprecated now.

To sum up, I always do it in viewDidDisappear. However, it depends on your requirements also, just like @Dirk said.

Andes answered 16/1, 2013 at 5:51 Comment(3)
Lots of people are still writing code for older versions of iOS than iOS6.... :-)Codex
In ARC you can use this code but without the line [super dealloc]; You can see more here: developer.apple.com/library/ios/#releasenotes/ObjectiveC/…Lynnette
What if you had a regular NSObject be the observer of a notification? Would you use dealloc in this case?Lucia
R
4

I think I found a reliable answer! I had to, as the answers above are ambiguous and seem contradicting. I looked through Cookbooks and Programming Guides.

First, the style of addObserver: in viewWillAppear: and removeObserver: in viewWillDisappear: does not work for me (I tested it) because I am posting a notification in a child view controller to execute code in the parent view controller. I would only use this style if I was posting and listening for the notification within the same view controller.

The answer I will rely on the most, I found in the iOS Programming: Big Nerd Ranch Guide 4th. I trust the BNR guys because they have iOS training centers and they are not just writing another cookbook. It is probably in their best interest to be accurate.

BNR example one: addObserver: in init:, removeObserver: in dealloc:

BNR example two: addObserver: in awakeFromNib:, removeObserver: in dealloc:

…when removing observer in dealloc: they don’t use [super dealloc];

I hope this helps the next person…

I am updating this post because Apple now has almost completely gone with Storyboards so the above mentioned may not apply to all situations. The important thing (and the reason I added this post in the first place) is to pay attention if your viewWillDisappear: is getting called. It wasn't for me when the application entered background.

Rabah answered 13/6, 2014 at 13:44 Comment(1)
It is hard to say if this is right as the context is important. It is mentioned a few times already, but dealloc makes little sense in an ARC context (which is the only context by now). It is also not predictable when dealloc is called - viewWillDisappear is easier to control. A side note: If your child needs to communicate something to it's parent the delegate pattern sounds like a better choice.Forzando
F
2

The accepted answer is not safe and could cause a memory leak. Please do leave the unregister in dealloc but also deregister in viewWillDisappear (that is of course if you register in viewWillAppear)....THAT'S WHAT I DID ANYWAYS AND IT WORKS GREAT! :)

Filum answered 27/6, 2013 at 20:3 Comment(1)
I agree with this answer. I experience memory warnings and leaks leading to crashes after intensive use of the app if I don't remove the observers in viewWillDisappear.Burtonburty
M
1

It is important to notice also that viewWillDisappear is called also when the view controller present a new UIView. This delegate simply indicate that the view controller main view is not visible on the display.

In this case, deallocating the notification in viewWillDisappear may be inconvenient if we are using the notification to allow the UIview to communicate with the parent view controller.

As a solution I usually remove the observer in one of these two methods:

- (void)viewWillDisappear:(BOOL)animated {
    NSLog(@"viewController will disappear");
    if ([self isBeingDismissed]) {
        NSLog(@"viewController is being dismissed");
        [[NSNotificationCenter defaultCenter] removeObserver:self name:@"actionCompleted" object:nil];
    }
}

-(void)dealloc {
    NSLog(@"viewController is being deallocated");
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"actionCompleted" object:nil];
}

For similar reasons, when I issue the notification the first time, I need to account for the fact that any time a view with appear above the controller then viewWillAppear method is fired. This will in turn generate multiple copy of the same notification. Since there isn't a way to check if a notification is already active, I obviate the problem by removing the notification before adding it:

- (void)viewWillAppear:(BOOL)animated {
    NSLog(@"viewController will appear");
    // Add observers
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"imageGenerated" object:nil]; // This is added to avoid duplicate notifications when the view is presented again
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedImageFromCameraOrPhotolibraryMethodOnListener:) name:@"actionCompleted" object:nil];

}
Messner answered 22/1, 2017 at 2:53 Comment(0)
H
0
override func viewDidLoad() {   //add observer
  super.viewDidLoad()
  NotificationCenter.default.addObserver(self, selector:#selector(Yourclassname.method), name: NSNotification.Name(rawValue: "NotificationIdentifier"), object: nil)
}

override func viewWillDisappear(_ animated: Bool) {    //remove observer
    super.viewWillDisappear(true)
    NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "NotificationIdentifier"), object: nil)
}
Hegemony answered 31/7, 2017 at 5:51 Comment(0)
F
-1

This is the only correct answer (all other are mistakenly suggest to use deinit and dealloc - this is clear misunderstanding of class instance lifecycle and iOS system).

Swift 5

There are two cases of using notifications:

  • they are needed only when the view controller is on screen (viewWillAppear + viewWillDisappear)
  • they are needed always, even if user opened another screen over current (viewDidLoad + viewWillDisappear with "if").

For the first case the correct place to add and remove observer are:

    /// Add observers
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        NotificationCenter.default.addObserver(...)
    }

    /// Remove observers
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

// remove when screen dismissed
        NotificationCenter.default.removeObserver(self) 
    }

for the second case the correct way is:

    /// Add observers
    override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(...)
    }

    /// Remove observers
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

// remove only when view controller is removed disappear forever
        if self.isBeingDismissed 
        || !(self.navigationController?.viewControllers.contains(self) ?? true) {
            NotificationCenter.default.removeObserver(self)
        }
    }

And never put removeObserver in deinit{ ... }, or dealloc - it's a MISTAKE!

Fritzsche answered 3/7, 2017 at 10:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.