Objective-C: Где удалить наблюдателя для NSNotification?


102

У меня объективный класс C. В нем я создал метод инициализации и настроил в нем NSNotification

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

Где мне установить [[NSNotificationCenter defaultCenter] removeObserver:self]в этом классе? Я знаю, что для a UIViewControllerя могу добавить его в viewDidUnloadметод. Итак, что нужно сделать, если я только что создал целевой класс c?


Ставлю в метод dealloc.
onnoweb

1
Метод dealloc не был автоматически создан для меня, когда я создавал целевой класс c, поэтому я могу добавить его?
Zhen

Да, вы можете реализовать, -(void)deallocа затем добавить removeObserser:selfв него. Это наиболее рекомендуемый способ поставитьremoveObservers:self
petershine

Можно ли использовать deallocметод в iOS 6?
wcochran

2
Да, использовать dealloc в проектах ARC можно, если вы не вызываете [super dealloc] (вы получите ошибку компилятора, если вызовете [super dealloc]). И да, вы определенно можете поместить свой removeObserver в dealloc.
Фил

Ответы:


112

Общий ответ будет «как только вам больше не нужны уведомления». Это явно не удовлетворительный ответ.

Я бы порекомендовал вам добавить вызов [notificationCenter removeObserver: self]в метод deallocтех классов, которые вы собираетесь использовать в качестве наблюдателей, так как это последний шанс полностью отменить регистрацию наблюдателя. Однако это защитит вас только от сбоев из-за уведомления центра уведомлений о мертвых объектах. Он не может защитить ваш код от получения уведомлений, когда ваши объекты еще не находятся / больше не находятся в состоянии, в котором они могут правильно обрабатывать уведомление. Для этого ... См. Выше.

Изменить (поскольку ответ, похоже, вызывает больше комментариев, чем я мог подумать) Все, что я пытаюсь сказать здесь, это: действительно сложно дать общий совет относительно того, когда лучше всего удалить наблюдателя из центра уведомлений, потому что это зависит от:

  • В вашем случае использования (какие уведомления наблюдаются? Когда они отправляются?)
  • Реализация наблюдателя (когда он готов получать уведомления? Когда он больше не готов?)
  • Предполагаемое время жизни наблюдателя (привязано ли оно к какому-либо другому объекту, скажем, представлению или контроллеру представления?)
  • ...

Итак, лучший общий совет, который я могу дать: защитите свое приложение. против хотя бы одного возможного отказа, сделайте removeObserver:танец dealloc, поскольку это последний момент (в жизни объекта), где вы можете сделать это чисто. Это не означает: «просто отложите удаление до deallocвызова, и все будет хорошо». Вместо этого удалите наблюдателя, как только объект больше не будет готов (или не требуется) для получения уведомлений . Это как раз подходящий момент. К сожалению, не зная ответов ни на один из упомянутых выше вопросов, я даже не могу предположить, когда это будет.

Вы всегда можете безопасно removeObserver:обрабатывать объект несколько раз (и все, кроме самого первого вызова с данным наблюдателем, будут nops). Итак: подумайте о том, чтобы сделать это (снова), deallocчтобы быть уверенным, но прежде всего: сделайте это в подходящий момент (который определяется вашим вариантом использования).


4
Это небезопасно для ARC и потенциально может вызвать утечку. См. Это обсуждение: cocoabuilder.com/archive/cocoa/311831-arc-and-dealloc.html
MobileMon

3
@MobileMon Статья, на которую вы ссылаетесь, похоже, подтверждает мою точку зрения. Что мне не хватает?
Dirk

Я полагаю, это следует отметить в том, что нужно удалить наблюдателя где-то еще, а не в dealloc. Например, вид исчезнет
MobileMon

1
@MobileMon - да. Я надеюсь, что это то, о чем я говорю со своим ответом. Удаление наблюдателя dealloc- это только последняя линия защиты от сбоя приложения из-за более позднего доступа к освобожденному объекту. Но подходящее место для отмены регистрации наблюдателя обычно находится в другом месте (и часто гораздо раньше в жизненном цикле объекта). Я не пытаюсь сказать здесь: «Эй, просто сделай это, deallocи все будет хорошо».
Дирк

@MobileMon "Например, viewWillDisappear" Проблема с конкретным советом в том, что это действительно зависит от того, какой тип объекта вы регистрируете в качестве наблюдателя для какого типа события. Это может быть правильным решением для отмены регистрации наблюдателя в viewWillDisappear(или viewDidUnload) на UIViewControllerс, но это действительно зависит от случая использования.
Dirk

39

Примечание: это было протестировано и работает на 100%.

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)
    }
}

ПредставленныйViewController

override func viewWillDisappear(animated: Bool){
    super.viewWillDisappear(animated)

    if self.isBeingDismissed()  //presented view controller
    {
        // remove observer here
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
}

Цель-C

В iOS 6.0 > version, лучше удалить наблюдателя, viewWillDisappearпоскольку viewDidUnloadметод устарел.

 [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];

Во многих случаях лучше, remove observerкогда представление было удалено из 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];
    }
}

ПредставленныйViewController

- (void)viewWillDisappear:(BOOL)animated{
    if ([self isBeingDismissed] == YES) ///presented view controller
    {
        // remove observer here
        [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];
    }
}

8
За исключением того, что контроллеру могут потребоваться уведомления, когда его представление не отображается (например, для перезагрузки tableView).
wcochran

2
@wcochran автоматически перезагружается / обновляетсяviewWillAppear:
Ричард

@ Принц, можешь ли ты объяснить, почему viewWillDisapper лучше, чем dealloc? Итак, у нас есть добавление наблюдателя к себе, поэтому, когда самость будет удалена из памяти, она вызовет dealloc, а затем все наблюдатели будут удалены, это плохая логика.
Матросов Александр

Вызов removeObserver:selfлюбого из UIViewControllerсобытий жизненного цикла почти гарантированно испортит вам неделю. Дополнительная литература: subjective-objective-c.blogspot.com/2011/04/…
cbowns

1
Ввод removeObserverвызовов, viewWillDisappearкак указано, определенно правильный путь, если контроллер представлен через pushViewController. Если вы вставите их deallocвместо этого, deallocони никогда не будут вызваны - по крайней мере, по моему опыту ...
Кристофер Кинг

38

Начиная с iOS 9, больше не нужно удалять наблюдателей.

В OS X 10.11 и iOS 9.0 NSNotificationCenter и NSDistributedNotificationCenter больше не будут отправлять уведомления зарегистрированным наблюдателям, которые могут быть освобождены.

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


2
Может быть, они не будут посылать сообщения наблюдателям, но я считаю, что они, насколько я понимаю, будут твердо ссылаться на них. В этом случае все наблюдатели останутся в памяти и произведут утечку. Поправьте меня, если я ошибаюсь.
ель

6
Связанная документация подробно описывает это. TL; DR: это слабая ссылка.
Себастьян

но, конечно, это все равно необходимо на случай, если вы сохраните объект, ссылающийся на них, и просто не хотите больше слушать уведомления
TheEye

25

Если наблюдатель добавлен к контроллеру представления , я настоятельно рекомендую добавить его viewWillAppearи удалить viewWillDisappear.


Мне любопытно, @RickiG: почему вы рекомендуете использовать viewWillAppearи viewWillDisappearдля viewControllers?
Исаак Оверакер,

2
@IsaacOveracker по нескольким причинам: ваш код настройки (например, loadView и viewDidLoad) потенциально может вызвать запуск уведомлений, и ваш контроллер должен отразить это до того, как он появится. Если вы сделаете это так, есть несколько преимуществ. В тот момент, когда вы решили «оставить» контроллер, вы не заботитесь об уведомлениях, и они не заставят вас выполнять логику, пока контроллер выталкивается с экрана и т. Д. Существуют особые случаи, когда контроллер должен получать уведомления, когда он за кадром, я думаю, ты не можешь этого сделать. Но подобные события, вероятно, должны быть в вашей модели.
RickiG

1
@IsaacOveracker также с ARC было бы странно реализовать dealloc для отказа от подписки на уведомления.
RickiG

4
Из тех, что я пробовал, с iOS7 это лучший способ зарегистрировать / удалить наблюдателей при работе с UIViewControllers. Единственная загвоздка в том, что во многих случаях вы не хотите, чтобы наблюдатель удалялся при использовании UINavigationController и помещении другого UIViewController в стек. Решение: вы можете проверить, появляется ли VC в viewWillDisappear, вызвав [self isBeingDismissed].
lekksi

Извлечение контроллера представления из контроллера навигации может не вызывать deallocнемедленного вызова. Возврат к контроллеру представления может затем вызвать несколько уведомлений, если наблюдатель добавлен в команды инициализации.
Джонатан Лин

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

4
Я бы изменил порядок этих инструкций ... Использование selfafter [super dealloc]заставляет меня нервничать ... (даже если получатель вряд ли действительно разыменует указатель каким-либо образом, ну, вы никогда не знаете, как они реализованы NSNotificationCenter)
Дирк

Хм. это сработало для меня. Вы заметили необычное поведение?
Леголас

1
Дирк прав - это неверно. [super dealloc]всегда должно быть последним утверждением вашего deallocметода. Он разрушает ваш объект; после его запуска у вас больше нет действительного self. / cc @Dirk
jscs

38
Если использовать ARC на iOS 5+, думаю, [super dealloc]он больше не нужен
pixelfreak

3
@pixelfreak сильнее, в ARC нельзя звонить [super dealloc]
tapmonkey


7

В быстром использовании deinit, потому что dealloc недоступен:

deinit {
    ...
}

Документация Swift:

Деинициализатор вызывается непосредственно перед освобождением экземпляра класса. Вы пишете деинициализаторы с ключевым словом deinit, подобно тому, как инициализаторы пишутся с ключевым словом init. Деинициализаторы доступны только для типов классов.

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


5

* изменить: этот совет относится к iOS <= 5 (даже там вы должны добавлять viewWillAppearи удалять viewWillDisappear- однако совет применяется, если по какой-то причине вы добавили наблюдателя viewDidLoad)

Если вы добавили наблюдателя в, viewDidLoadвы должны удалить его как в, так deallocи в viewDidUnload. В противном случае вы в конечном итоге добавите его дважды, когда viewDidLoadбудет вызван after viewDidUnload(это произойдет после предупреждения памяти). Это не обязательно в iOS 6, где viewDidUnloadона устарела и не будет вызываться (поскольку представления больше не выгружаются автоматически).


2
Добро пожаловать в StackOverflow. Ознакомьтесь с FAQ по MarkDown (значок вопросительного знака рядом с полем редактирования вопроса / ответа). Использование Markdwon повысит удобство использования вашего ответа.
marko

5

На мой взгляд, следующий код не имеет смысла в ARC :

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

В iOS 6 также нет смысла удалять наблюдателей в viewDidUnload, потому что теперь он устарел.

Подводя итог, я всегда делаю это внутри viewDidDisappear. Однако это также зависит от ваших требований, как сказал @Dirk.


Многие люди все еще пишут код для более старых версий iOS, чем iOS6 .... :-)
lnafziger

В ARC вы можете использовать этот код, но без строки [super dealloc]; Вы можете увидеть больше здесь: developer.apple.com/library/ios/#releasenotes/ObjectiveC/…
Alex

1
Что, если бы у вас был обычный NSObject в качестве наблюдателя за уведомлением? Вы бы использовали в этом случае dealloc?
qix

4

Думаю, я нашел надежный ответ ! Мне пришлось, поскольку приведенные выше ответы неоднозначны и кажутся противоречивыми. Я просмотрел поваренные книги и руководства по программированию.

Во-первых, стиль addObserver:in viewWillAppear:и removeObserver:in viewWillDisappear:у меня не работает (я тестировал его), потому что я отправляю уведомление в контроллер дочернего представления для выполнения кода в контроллере родительского представления. Я бы использовал этот стиль только в том случае, если бы отправлял и слушал уведомление в том же контроллере представления.

Ответ, на который я буду полагаться больше всего, я нашел в книге «Программирование для iOS: руководство Big Nerd Ranch Guide 4th». Я доверяю ребятам из BNR, потому что у них есть учебные центры iOS, и они не просто пишут очередную кулинарную книгу. Вероятно, в их интересах быть точными.

БНР, пример первый: addObserver:в init:, removeObserver:вdealloc:

БНР, пример второй: addObserver:в awakeFromNib:, removeObserver:вdealloc:

… При удалении наблюдателя dealloc:они не используют[super dealloc];

Надеюсь, это поможет следующему человеку ...

Я обновляю этот пост, потому что Apple теперь почти полностью отказалась от раскадровок, поэтому вышеупомянутое может не относиться ко всем ситуациям. Важная вещь (и причина, по которой я добавил этот пост в первую очередь) - обратить внимание, если вам viewWillDisappear:звонят. Когда приложение вошло в фоновый режим, это было не для меня.


Трудно сказать, правильно ли это, поскольку важен контекст. Об этом уже упоминалось несколько раз, но dealloc не имеет смысла в контексте ARC (который на данный момент является единственным контекстом). Также непредсказуемо, когда вызывается dealloc - viewWillDisappear легче контролировать. Примечание: если вашему ребенку нужно что-то сообщить своему родителю, то шаблон делегата кажется лучшим выбором.
RickiG

2

Принятый ответ небезопасен и может вызвать утечку памяти. Пожалуйста, оставьте отмену регистрации в dealloc, но также отмените регистрацию в viewWillDisappear (конечно, если вы зарегистрируетесь в viewWillAppear) .... Я ВСЕГДА ДЕЛАЛ ЭТО ТАКОЕ, И ЭТО РАБОТАЕТ ОТЛИЧНО! :)


1
Я согласен с этим ответом. Я получаю предупреждения и утечки памяти, ведущие к сбоям после интенсивного использования приложения, если я не удаляю наблюдателей в viewWillDisappear.
SarpErdag

2

Также важно отметить, что это viewWillDisappearвызывается также, когда контроллер представления представляет новый UIView. Этот делегат просто указывает, что основное представление контроллера представления не отображается на дисплее.

В этом случае удаление уведомления viewWillDisappearможет быть неудобным, если мы используем уведомление, чтобы позволить UIview взаимодействовать с родительским контроллером представления.

В качестве решения я обычно удаляю наблюдателя одним из этих двух методов:

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

По тем же причинам, когда я отправляю уведомление в первый раз, мне нужно учитывать тот факт, что каждый раз, когда над контроллером появляется представление с, тогда viewWillAppearметод запускается. Это, в свою очередь, создаст несколько копий одного и того же уведомления. Поскольку нет способа проверить, активно ли уведомление, я устраняю проблему, удаляя уведомление перед его добавлением:

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

}

-1

SWIFT 3

Есть два случая использования уведомлений: - они нужны только тогда, когда контроллер представления находится на экране; - они нужны всегда, даже если пользователь открыл другой экран поверх текущего.

В первом случае правильное место для добавления и удаления наблюдателя:

/// Add observers
///
/// - Parameter animated: the animation flag
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    NotificationCenter.default.addObserver(...)
}

/// Remove observers
///
/// - Parameter animated: the animation flag
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    NotificationCenter.default.removeObserver(self)
}

для второго случая правильный способ:

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

/// Remove observers
///
/// - Parameter animated: the animation flag
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if self.isBeingDismissed // remove only when view controller is removed disappear forever
    || !(self.navigationController?.viewControllers.contains(self) ?? true) {
        NotificationCenter.default.removeObserver(self)
    }
}

И никогда не ставили removeObserverв deinit{ ... }- это ОШИБКА!


-1
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)
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.