Завдання-C: Де зняти спостерігача за NSNotification?


102

У мене об’єктивний клас C. У ньому я створив метод init і встановив у ньому NSNotification

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

Де мені встановити [[NSNotificationCenter defaultCenter] removeObserver:self]цей клас? Я знаю, що для a UIViewControllerя можу додати його до viewDidUnloadметоду. Отже, що потрібно зробити, якщо я щойно створив об'єктивний c Class?


Я поклав це в метод dealloc.
onnoweb

1
Метод dealloc не був створений автоматично для мене, коли я створював об'єктивний клас c, тому мені нормально його додати?
Чень

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

Чи все-таки добре ввести deallocметод в iOS 6?
wcochran

2
Так, нормально використовувати dealloc в проектах ARC до тих пір, поки ви не зателефонуєте [super dealloc] (ви отримаєте помилку компілятора, якщо зателефонуєте [super dealloc]). І так, ви точно можете поставити свого RemoveObserver у взаємодію.
Філ

Відповіді:


112

Загальна відповідь буде "як тільки вам більше не потрібні сповіщення". Це, очевидно, не задовольняє відповідь.

Я рекомендую вам додати виклик [notificationCenter removeObserver: self]у метод deallocтих класів, які ви маєте намір використовувати як спостерігачі, оскільки це останній шанс чисто зареєструвати спостерігача. Однак це захистить вас лише від збоїв через те, що центр сповіщення повідомляє про мертві об’єкти. Він не може захистити ваш код від отримання сповіщень, якщо ваші об'єкти ще не перебувають у стані, в якому вони можуть належним чином обробляти сповіщення. Для цього ... Дивіться вище.

Редагувати (оскільки, здається, відповідь викликає більше коментарів, ніж я б міг подумати). Все, що я намагаюся сказати тут, - це дійсно важко дати загальну пораду щодо того, коли найкраще видалити спостерігача з центру сповіщень, адже це залежить від:

  • У вашому випадку використання (Які сповіщення спостерігаються? Коли їх надсилають?)
  • Реалізація спостерігача (Коли він готовий отримувати повідомлення? Коли він більше не готовий?)
  • Передбачуваний життєвий час спостерігача (чи прив'язаний він до якогось іншого об'єкта, скажімо, контролера перегляду чи перегляду?)
  • ...

Отож, найкраща загальна порада, яку я можу запропонувати: захистити ваш додаток. проти хоча б однієї можливої ​​невдачі, займайтеся removeObserver:танцями dealloc, оскільки це останній момент (у житті об'єкта), де ви можете це робити чисто. Що це не означає: "просто відкладіть видалення, поки не deallocбуде викликано, і все буде добре". Натомість видаліть спостерігача, як тільки об’єкт більше не готовий (або потрібен) для отримання сповіщень . Це саме правильний момент. На жаль, не знаючи відповідей на жодне з вищезазначених питань, я навіть не можу здогадатися, коли це станеться.

Ви завжди можете removeObserver:безперешкодно захищати об'єкт кілька разів (і всі, окрім першого дзвінка з даним спостерігачем, будуть носочками). Отже: подумайте про те, щоб зробити це (знову) deallocлише для того, щоб бути впевненим, але в першу чергу: зробіть це у відповідний момент (що визначається вашим випадком використання).


4
Це не є безпечним для ARC і може потенційно спричинити витік. Дивіться цю дискусію: cocoabuilder.com/archive/cocoa/311831-arc-and-dealloc.html
MobileMon

3
@MobileMon Стаття, з якою ви посилаєтесь, здається, мою думку. Що я пропускаю?
Дірк

Я припускаю, що слід зазначити, що слід видалити спостерігача деінде, окрім угоди. Наприклад, viewwilldisappear
MobileMon

1
@MobileMon - так. Я сподіваюся, що саме в цьому я зіткнувся зі своєю відповіддю. Видалення спостерігача в dealloc- це лише остання лінія захисту від аварійного завершення роботи програми через пізніший доступ до об'єкта, що виділяється. Але належне місце для скасування реєстрації спостерігача зазвичай є десь в іншому місці (і часто, набагато раніше в життєвому циклі об'єкта). Я не намагаюся сказати тут "Гей, просто зробіть це, deallocі все буде добре".
Дірк

@MobileMon "Наприклад, viewWillDisappear" Проблема з наданням конкретної поради полягає в тому, що це дійсно залежить від того, який саме об'єкт ви реєструєте в якості спостерігача для якої події. Це може бути правильним рішенням для скасування реєстрації спостерігача в viewWillDisappear(або viewDidUnload) на UIViewControllerс, але це дійсно залежить від випадку використання.
Дірк

39

Примітка. Це перевірено і працює на 100%

Швидкий

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

Ціль-С

У цьому 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
За винятком того, що контролер може все ще бажати сповіщень, коли його перегляд не відображається (наприклад, для перезавантаження таблиціView).
wcochran

2
@wcochran автоматично перезавантажує / оновитьviewWillAppear:
Річард

@Prince, чи можете ви пояснити, чому viewWillDisapper краще, ніж вирішувати? тому ми додаємо спостерігача до себе, тож коли я випаде із пам’яті, він зателефонує у взаємодію, і тоді всі спостерігачі будуть видалені, це не є хорошою логікою.
Матросов Олександр

Зателефонувавши 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 до стеку. Рішення: Ви можете перевірити, чи вимкнений ВК у viewWillDisappear, зателефонувавши [self isBeingDismissed].
lekksi

Вискакування контролера виду з контролера навігації може не викликати deallocнегайний виклик. Повернення назад до контролера перегляду може призвести до отримання декількох сповіщень, якщо спостерігач буде доданий до команд ініціалізації.
Джонатан Лін

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

4
Я б перетворив порядок цих інструкцій ... Використання selfпісля [super dealloc]мене нервує ... (навіть якщо приймач навряд чи дійсно знеструмить вказівник, ну, ви ніколи не знаєте, як вони реалізовані NSNotificationCenter)
Дірк

Гм. це спрацювало на мене. Ви помітили якусь незвичну поведінку?
Леголас

1
Дірк має рацію - це неправильно. [super dealloc]завжди має бути останнім твердженням вашого deallocметоду. Це руйнує ваш об’єкт; після його запуску ви більше не маєте дійсного self. / cc @Dirk
jscs

38
Якщо ви використовуєте ARC на iOS 5+, я думаю, [super dealloc]це більше не потрібно
pixelfreak

3
@pixelfreak сильніше, під ARC заборонено дзвонити [супер угода]
tapmonkey


7

Швидко використовуйте deinit, оскільки dealloc недоступний:

deinit {
    ...
}

Швидка документація:

Дезініціалізатор викликається безпосередньо перед розміщенням екземпляра класу. Ви пишете деініціалізатори з ключовим словом deinit, подібно до того, як пишеться intializer за допомогою ключового слова init. Дезініалізатори доступні лише для типів класу.

Зазвичай вам не потрібно проводити очищення вручну, коли ваші екземпляри розміщені. Однак, працюючи з власними ресурсами, можливо, вам доведеться виконати додаткову очистку самостійно. Наприклад, якщо ви створюєте спеціальний клас, щоб відкрити файл і записати до нього деякі дані, можливо, вам доведеться закрити файл до того, як екземпляр класу буде розміщений.


5

* редагувати: Ця порада стосується iOS <= 5 (навіть там ви повинні додавати viewWillAppearта видаляти viewWillDisappear- проте порада застосовується, якщо ви з якоїсь причини додали спостерігача viewDidLoad)

Якщо ви додали спостерігача, viewDidLoadслід видалити його в обох deallocі viewDidUnload. Інакше ви додасте його двічі, коли viewDidLoadбуде викликано після viewDidUnload(це станеться після попередження пам'яті). Це не потрібно в iOS 6, де viewDidUnloadзастаріле і не буде викликано (тому що перегляди більше не завантажуються автоматично).


2
Ласкаво просимо до StackOverflow. Ознайомтесь із поширеними запитаннями щодо 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 був спостерігачем сповіщення? Чи використовуєте ви у цій справі угода?
qix

4

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

По-перше, стиль addObserver:в viewWillAppear:і removeObserver:вviewWillDisappear: не працює для мене (я тестував) , тому що я відправляю повідомлення в контролері уявлення дитини для виконання коду в контролері батьківського виду. Я використовував би цей стиль лише у випадку, коли я публікував та слухав сповіщення в межах одного контролера перегляду.

Відповідь, на яку я буду покладатися найбільше, я знайшов у програмуванні iOS: Big Nerd Ranch Guide 4th. Я довіряю хлопцям БНР, оскільки вони мають навчальні центри iOS, і вони не просто пишуть ще одну кулінарну книгу. Напевно, їм в інтересах бути точними.

Приклад БНР перший: addObserver:в init:, removeObserver:вdealloc:

Приклад БНР другий: addObserver:в awakeFromNib:, removeObserver:вdealloc:

... при вилученні спостерігача dealloc:вони не використовують[super dealloc];

Я сподіваюся, що це допоможе наступній людині ...

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


Важко сказати, чи правильно це, оскільки важливий контекст. Про це вже згадується декілька разів, але у контексті АРК (що є єдиним контекстом на сьогоднішній день) малець має мало сенсу. Це також не передбачувано, коли викликається 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.