iOS Remove observer from notification: Can I call this once for all observers? And even if there are none?
Asked Answered
P

2

34

I'm registering three observers in most of my view controllers. Some have more, some less but I want to include part of the registration and unregistration process in a parent class. Is there any problem with calling the unregistering even if there is no observer? And is one call to unregister enough for all three observers?

- (void)registerForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWasShown:)
                                                 name:UIKeyboardWillShowNotification object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillBeHidden:)
                                                 name:UIKeyboardWillHideNotification object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(applicationWillEnterBackground:)
                                                 name:UIApplicationWillResignActiveNotification
                                               object:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    //Has to be unregistered always, otherwise nav controllers down the line will call this method
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
Parenthesis answered 28/1, 2014 at 22:56 Comment(1)
Yes, that should be fine.Alves
A
61

Yes, that will remove all registrations where the observer is self. It's documented in the NSNotificationCenter Class Reference:

The following example illustrates how to unregister someObserver for all notifications for which it had previously registered:

[[NSNotificationCenter defaultCenter] removeObserver:someObserver];

Note that in theory (but not, as far as I know, in practice as of iOS 7.0), UIViewController could have its own registrations that it doesn't want removed in viewWillDisappear:. It's unlikely to register for any of the notifications in the public API using addObserver:selector:name:object:, because that would preclude you registering for them in your UIViewController subclass, but it could certainly register for non-public notifications now or in a future version.

A safe way to deregister is to send removeObserver:name:object: once for each registration:

- (void)deregisterForKeyboardNotifications {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center removeObserver:self name:UIKeyboardWillShowNotification object:nil];
    [center removeObserver:self name:UIKeyboardWillHideNotification object:nil];
    [center removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [self deregisterForKeyboardNotifications];
}

- (void)dealloc {
    [self deregisterForKeyboardNotifications];
}

Another way is to use addObserverForName:object:queue:usingBlock: to register (instead of addObserver:selector:name:object:). This returns a new observer object reference for each registration. You have to save these away (perhaps in an NSArray instance variable if you don't want to create individual instance variables). Then you pass each one to removeObserver: to deregister its notification. Example:

@implementation MyViewController {
    NSMutableArray *observers;
}

- (void)registerForKeyboardNotifications {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    __weak MyViewController *me = self;
    observers = [NSMutableArray array];
    [observers addObject:[center addObserverForName:UIKeyboardWillShowNotification
        object:nil queue:queue usingBlock:^(NSNotification *note) {
            [me keyboardWillShow:note];
        }]];
    [observers addObject:[center addObserverForName:UIKeyboardWillHideNotification
        object:nil queue:queue usingBlock:^(NSNotification *note) {
            [me keyboardWillHide:note];
        }]];
    [observers addObject:[center addObserverForName:UIApplicationWillResignActiveNotification
        object:nil queue:queue usingBlock:^(NSNotification *note) {
            [me applicationWillResignActive:note];
        }]];
}

- (void)deregisterForKeyboardNotifications {
    for (id observer in observers) {
        [[NSNotificationCenter defaultCenter] removeObserver:observer];
    }
    observers = nil;
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [self deregisterForKeyboardNotifications];
}

- (void)dealloc {
    [self deregisterForKeyboardNotifications];
}

Since every observer returned by addObserverForName:object:queue:usingBlock: is a new object that has only one registration, each call to removeObserver: is guaranteed to only remove that observer's one registration.

Update for iOS 9 / macOS 10.11 and later

As of iOS 9 and macOS 10.11, NSNotificationCenter automatically deregisters an observer if the observer is deallocated. It is no longer necessary to deregister yourself manually in your dealloc method (or deinit in Swift) if your deployment target is iOS 9 or later or macOS 10.11 or later.

Angelicaangelico answered 28/1, 2014 at 23:12 Comment(5)
Ok thx. But this is only theory? Or is there a reasonable danger that I could remove some important observers? How would you differentiate then between your observers and iOS observers?Parenthesis
As I said, as far as I know, UIViewController doesn't actually use any notifications as of iOS 7.0. I have updated my answer.Angelicaangelico
Very valuable post. Thx alot. And if I remove the observers more specifically as you mentioned with [center removeObserver:self name:UIKeyboardWillShowNotification object:nil] it isn't a problem either if they were never added?Parenthesis
Correct. That will not be a problem.Angelicaangelico
Hi anyone can explain this related to iOS, My app was getting crashed because of observers and after I removed the observer its not now. I'm adding a controller as an observer to some view. Is it for NotificationCenter only?Droopy
N
7

For your first Question unregistering even when there is no observer is OK. But for the way you're removing the observer, [[NSNotificationCenter defaultCenter] removeObserver:someObserver]; will remove even the super class observers which is highly unrecommended (except in dealloc because the object is unloaded) but in viewWillDisappear you should remove the observers one by one by using [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];

Nevins answered 28/1, 2014 at 23:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.