iOS Удалить наблюдателя из уведомления: могу ли я называть это один раз для всех наблюдателей? И даже если их нет?

Я регистрирую трех наблюдателей в большинстве контроллеров моего представления. У некоторых есть больше, несколько меньше, но я хочу включить часть процесса регистрации и отмены регистрации в родительский класс. Есть ли проблема с вызовом регистрации, даже если нет наблюдателя? И один призыв к регистрации достаточно для всех трех наблюдателей?

- (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];
}

Ответ 1

Да, это приведет к удалению всех регистраций, где наблюдатель self. Он задокументирован в справочнике классов NSNotificationCenter:

В следующем примере показано, как someObserver регистрацию someObserver для всех уведомлений, для которых он ранее зарегистрировался:

[[NSNotificationCenter defaultCenter] removeObserver:someObserver];

Обратите внимание, что в теории (но не, насколько я знаю, на практике, как iOS 7.0), UIViewController может иметь свои собственные регистрации, которые он не хочет удалять в viewWillDisappear: Он вряд ли зарегистрируется для любого из уведомлений в публичном API с помощью addObserver:selector:name:object: потому что это помешает вам зарегистрироваться для них в вашем подклассе UIViewController, но он может, безусловно, зарегистрироваться для непубличных уведомлений сейчас или в будущая версия.

Безопасный способ removeObserver:name:object: регистрации - отправить removeObserver:name:object: один раз для каждой регистрации:

- (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];
}

Другой способ - использовать addObserverForName:object:queue:usingBlock: для регистрации (вместо addObserver:selector:name:object:. Это возвращает новую ссылку на объект-наблюдатель для каждой регистрации. Вы должны сохранить их (возможно, в NSArray экземпляра NSArray если вы не хотите создавать отдельные переменные экземпляра). Затем вы передаете каждый из них на removeObserver: чтобы removeObserver: его уведомление. Пример:

@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];
}

Поскольку каждый наблюдатель возвращается с помощью addObserverForName:object:queue:usingBlock: это новый объект, который имеет только одну регистрацию, каждый вызов removeObserver: гарантированно удаляет только эту наблюдателя одну регистрацию.

Обновление для iOS 9/macOS 10.11 и более поздних версий

Начиная с iOS 9 и macOS 10.11, NSNotificationCenter автоматически NSNotificationCenter регистрацию наблюдателя, если наблюдатель освобождается. Вам больше не нужно отменять регистрацию вручную в своем методе dealloc (или deinit в Swift), если целью развертывания является iOS 9 или более поздняя версия или macOS 10.11 или новее.

Ответ 2

Для вашего первого вопроса без регистрации, даже если нет наблюдателя, все в порядке. Но для того, как вы [[NSNotificationCenter defaultCenter] removeObserver:someObserver]; наблюдателя, [[NSNotificationCenter defaultCenter] removeObserver:someObserver]; удалит даже суперклассических наблюдателей, которые крайне неоправданы (за исключением dealloc, потому что объект выгружен), но в viewWillDisappear вы должны viewWillDisappear удалять наблюдателей с помощью [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];