Добавление и удаление наблюдателей в NSNotificationCenter в UIViewController

Рассматривая различные примеры Apple (например, Добавить музыку ), в котором я вижу, что они добавляют наблюдателей по умолчанию NSNotificationCenter в viewDidLoad, а затем удаляют их в dealloc. Это кажется опасным, поскольку viewDidLoad может вызываться несколько раз без вызова dealloc. Затем один и тот же наблюдатель будет добавлен несколько раз, что приведет к многократному вызову обработчика.

Решением этой проблемы может быть также удаление наблюдателей в viewDidUnload, но тогда это будет означать, что один и тот же наблюдатель может быть удален во второй раз в dealloc, что кажется потенциальной проблемой.

Что мне не хватает?


person Undistraction    schedule 26.04.2012    source источник
comment
Я не думаю, что удаление несуществующего наблюдателя имеет какой-либо неблагоприятный эффект.   -  person jbat100    schedule 26.04.2012
comment
@ jbat100 Спасибо. Я определенно думаю, что удаление наблюдателей как в Dealloc, так и в viewDidUnload - единственный выход.   -  person Undistraction    schedule 26.04.2012


Ответы (4)


Существует много дискуссий о правильном удалении уведомлений. Например:

Я предлагаю вам удалить наблюдателей в viewWillDisappear (или viewDidDisappear) и viewDidUnload методах жизненного цикла. (Примечание. viewDidUnload устарело и не должно реализовываться в iOS6+; см. iOS 6 — viewDidUnload мигрировать на didReceiveMemoryWarning?)

Важное примечание:

viewDidUnload вызов не гарантируется — это не стандартный метод жизненного цикла.

Из документа Apple:

viewDidUnload Когда возникает состояние нехватки памяти и представления текущего контроллера представления не нужны, система может выбрать удаление этих представлений из памяти. Этот метод вызывается после того, как представление контроллера представления было освобождено, и это ваш шанс выполнить окончательную очистку.

Вместо этого dealloc вызывается всякий раз, когда количество ссылок для этого приемника равно нулю.

Надеюсь, поможет.

Изменить

Для полноты вы можете увидеть эту ссылку о том, как avoid-nsnotification-removeobserver. Ссылка содержит некоторые полезные рекомендации по удалению наблюдателя (см. также комментарии). Автор делает это в методах viewDidAppear/viewDidDisappear, так как viewWillAppear и viewWillDisappear не всегда корректно вызываются во многих приложениях. Это твой выбор.

Если вы хотите быть уверены, что правильно удалили наблюдателей, отмените регистрацию в методе dealloc или когда представление полностью выгрузится, как вы написали во втором комментарии. Но будьте уверены, что в будущем будет вызываться dealloc. Другими словами, как я уже упоминал, если контроллер продолжает оставаться в живых, поскольку какой-то другой объект ссылается на него, метод никогда не будет вызван. В этом случае контроллер продолжает получать уведомления.

person Lorenzo B    schedule 26.04.2012
comment
Спасибо. Но, как вы упомянули; 'viewDidUnload не вызывается перед методом Dealloc. Иногда деаллок VC будет вызываться без вызова viewDidUnload, и в этой ситуации использование вашего предложения означает, что VC остается в качестве наблюдателя после того, как он будет освобожден, поскольку viewWillDisappear / viewDidunload никогда не будет вызываться. Или viewWillDisappear абсолютно гарантированно будет вызываться перед деалоком? - person Undistraction; 26.04.2012
comment
@1ndivisible viewWillDisappear вызывается перед тем, как представление исчезнет с экрана. Это означает, что когда вы удаляете контроллер представления с экрана (например, контроллер извлекается из контроллера навигации), вызывается этот метод. Я добавил редактирование для вас. - person Lorenzo B; 26.04.2012
comment
Хороший. Ссылка избежать-nsnotification-removeobserver — хорошая. Понятия не имел. - person Undistraction; 26.04.2012
comment
Вы должны быть уверены, что dealloc вызывается независимо от того, используете ли вы уведомления или нет! Учитывая, что если ваша программа работает правильно, то она обязательно будет вызвана при освобождении объекта, dealloc - лучшее место для отмены регистрации, что делает init (или его вариант) единственным местом для регистрации. - person Craig McMahon; 27.04.2013
comment
@mcmahon да, ты прав. Я добавил правку для этого. Спасибо за ваш комментарий. - person Lorenzo B; 27.04.2013
comment
Я до сих пор не совсем понимаю, почему, но добавление и удаление наблюдателя в viewWillAppear/Disappear иногда приводило к сбою моего приложения. Перемещение их в viewDidAppear/Disappear исправило это для меня. - person Pier-Luc Gendreau; 21.11.2013
comment
Стоит отметить, что viewDidUnload устарел и не вызывается с iOS6! Я отредактировал ответ, чтобы добавить примечание об этом. - person occulus; 06.11.2014

Для тех, кто недавно наткнулся на эту страницу, удаление наблюдателей может больше не понадобиться. В разделе "Обсуждение" addObserver(_:selector:name:object:) документов говорится:

Если ваше приложение предназначено для iOS 9.0 и более поздних версий или macOS 10.11 и более поздних версий, вам не нужно отменять регистрацию наблюдателя в его методе dealloc. В противном случае вы должны вызвать removeObserver(_:name:object:) до того, как наблюдатель или любой объект, переданный этому методу, будет освобожден.

person Cem Schemel    schedule 06.02.2018
comment
Что ж, спасибо за это. У меня была проблема, когда мой viewWillDisappear вызывался без viewWillAppear, и он давал сбой. - person Oded; 07.12.2018

Почему бы вам не сделать это в viewWillAppear / viewDidDisappear? Вы заботитесь об уведомлениях только тогда, когда ваше представление все равно отображается, верно?

person mprivat    schedule 26.04.2012
comment
Но, насколько мне известно, нет никакой гарантии, что viewDidDisappear будет вызван, если объект будет освобожден. Если это не так, объект остается в качестве наблюдателя после его освобождения. - person Undistraction; 26.04.2012
comment
Видимо, они всегда будут называться. (см. выше) - person Undistraction; 26.04.2012
comment
Нет, в некоторых приложениях нужно заботиться о том, чтобы представление не отображалось, т.е. обновляло состояние данных. - person jini; 23.12.2012
comment
Если адресная книга обновляется, когда приложение находится в фоновом режиме или даже если мое приложение находится в другом представлении, мне все равно нужно уведомление. Таким образом, viewDidDisappear не подходит. - person T.J.; 08.02.2014
comment
Нет, я также забочусь о представлении, которое скрыто другим представлением - person n13; 23.08.2016

Вы можете добавить Observer в viewWillAppear и удалить Observer в viewWillDisappear. но viewWillAppear может вызываться много раз. так что вы можете сначала удалить уведомление, а затем addObserver.

 -(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:YES];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"UIKeyboardWillShowNotification" object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"UIKeyboardWillHideNotification" object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillShow:)name:@"UIKeyboardWillShowNotification"object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillHide:)name:@"UIKeyboardWillHideNotification"object:nil];
 }

 -(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:YES];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"UIKeyboardWillShowNotification" object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"UIKeyboardWillHideNotification" object:nil];
 }
person Sola Zhou    schedule 16.09.2015