Cel-C: Gdzie usunąć obserwatora do NSNotification?

102

Mam obiektywną klasę C. W nim stworzyłem metodę init i ustawiłem w niej NSNotification

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

Gdzie mam ustawić [[NSNotificationCenter defaultCenter] removeObserver:self]w tej klasie? Wiem, że w przypadku a UIViewControllermogę dodać to do viewDidUnloadmetody. Co więc należy zrobić, jeśli właśnie utworzyłem obiektywną klasę c?

Zhen
źródło
Umieściłem to w metodzie dealloc.
onnoweb
1
Metoda dealloc nie została automatycznie utworzona podczas tworzenia docelowej klasy c, więc mogę ją dodać?
Zhen
Tak, możesz to zaimplementować, -(void)dealloca następnie dodać removeObserser:self. Jest to najbardziej zalecany sposóbremoveObservers:self
petershine
Czy nadal można wprowadzić deallocmetodę w iOS 6?
wcochran
2
Tak, użycie dealloc w projektach ARC jest w porządku, o ile nie wywołujesz [super dealloc] (otrzymasz błąd kompilatora, jeśli wywołasz [super dealloc]). I tak, zdecydowanie możesz ustawić swój removeObserver w dealloc.
Phil

Odpowiedzi:

112

Ogólna odpowiedź brzmiałaby: „gdy tylko nie będziesz już potrzebować powiadomień”. To oczywiście nie jest satysfakcjonująca odpowiedź.

Poleciłbym dodać wywołanie [notificationCenter removeObserver: self]w metodzie dealloctych klas, których zamierzasz używać jako obserwatorów, ponieważ jest to ostatnia szansa na czyste wyrejestrowanie obserwatora. To jednak ochroni Cię tylko przed awariami, ponieważ centrum powiadomień powiadamia o martwych obiektach. Nie może chronić Twojego kodu przed otrzymywaniem powiadomień, gdy Twoje obiekty nie są jeszcze / nie są w stanie, w którym mogą poprawnie obsłużyć powiadomienie. Do tego ... Patrz wyżej.

Edytuj (ponieważ odpowiedź wydaje się przyciągać więcej komentarzy, niż bym myślał) Wszystko, co próbuję tutaj powiedzieć, to: naprawdę trudno jest udzielić ogólnej porady, kiedy najlepiej usunąć obserwatora z centrum powiadomień, ponieważ to zależy:

  • W Twoim przypadku użycia (Które powiadomienia są obserwowane? Kiedy są wysyłane?)
  • Realizacja obserwatora (Kiedy jest gotowy do otrzymywania powiadomień? Kiedy nie jest już gotowy?)
  • Planowany czas życia obserwatora (czy jest powiązany z jakimś innym obiektem, powiedzmy, widokiem lub kontrolerem widoku?)
  • ...

A więc najlepsza ogólna rada, jaką mogę wymyślić: ochrona aplikacji. przeciwko przynajmniej jednej możliwej awarii, removeObserver:zatańcz dealloc, ponieważ to ostatni punkt (w życiu obiektu), w którym możesz to zrobić czysto. Nie oznacza to: „po prostu odłóż usunięcie do momentu deallocwywołania, a wszystko będzie dobrze”. Zamiast tego usuń obserwatora, gdy tylko obiekt nie jest już gotowy (lub wymagany) do otrzymywania powiadomień . To jest właściwy moment. Niestety, nie znając odpowiedzi na którekolwiek z powyższych pytań, nie potrafię nawet zgadnąć, kiedy to nastąpi.

Zawsze możesz bezpiecznie removeObserver:obiekt wiele razy (i wszystkie oprócz pierwszego wywołania z danym obserwatorem to nops). A więc: pomyśl o zrobieniu tego (ponownie) deallocdla pewności, ale przede wszystkim: zrób to w odpowiednim momencie (który jest określony przez twój przypadek użycia).

Sztylet
źródło
4
Nie jest to bezpieczne w przypadku ARC i może potencjalnie spowodować wyciek. Zobacz tę dyskusję: cocoabuilder.com/archive/cocoa/311831-arc-and-dealloc.html
MobileMon
3
@MobileMon Wydaje się, że artykuł, do którego utworzyłeś łącze, zawiera mój punkt widzenia. Czego mi brakuje ?
Dirk
Przypuszczam, że należy zauważyć, że należy usunąć obserwatora w inne miejsce niż dealloc. Na przykład wyświetlenie zniknie
MobileMon
1
@MobileMon - tak. Mam nadzieję, że o to właśnie chodzi w mojej odpowiedzi. Usunięcie obserwatora w deallocto tylko ostatnia linia obrony przed awarią aplikacji z powodu późniejszego dostępu do nieprzydzielonego obiektu. Ale właściwym miejscem do wyrejestrowania obserwatora jest zwykle gdzie indziej (i często znacznie wcześniej w cyklu życia obiektu). Nie próbuję tu powiedzieć „Hej, po prostu zrób to dealloci wszystko będzie dobrze”.
Dirk,
@MobileMon "Na przykład viewWillDisappear" Problem z udzieleniem konkretnej porady polega na tym, że tak naprawdę zależy to od rodzaju obiektu, który zarejestrujesz jako obserwatora dla jakiego rodzaju zdarzenia. Może to być właściwe rozwiązanie, aby wyrejestrować obserwatora z viewWillDisappear(lub viewDidUnload) dla UIViewControllers, ale to naprawdę zależy od przypadku użycia.
Dirk
39

Uwaga: to zostało przetestowane i działa w 100%

Szybki

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

PresentedViewController

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

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

Cel C

W programie iOS 6.0 > versionlepiej jest usunąć obserwatora, viewWillDisappearponieważ viewDidUnloadmetoda jest przestarzała.

 [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];

Wiele razy lepiej jest, remove observergdy widok został usunięty z pliku 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];
    }
}

PresentedViewController

- (void)viewWillDisappear:(BOOL)animated{
    if ([self isBeingDismissed] == YES) ///presented view controller
    {
        // remove observer here
        [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];
    }
}
Paresh Navadiya
źródło
8
Z wyjątkiem tego, że kontroler może nadal chcieć powiadomień, gdy jego widok nie jest wyświetlany (np. W celu ponownego załadowania tableView).
wcochran
2
@wcochran automatycznie przeładowuje / odświeżaviewWillAppear:
Richard
@Książę, czy możesz wyjaśnić, dlaczego viewWillDisapper lepiej niż dealloc? więc dodaliśmy obserwatora do siebie, więc kiedy jaźń zostanie usunięta z pamięci, wywoła ona dealloc, a następnie wszyscy obserwatorzy zostaną usunięci, czy to nie jest dobra logika.
Matrosov Alexander
Wezwanie removeObserver:selfdo dowolnego UIViewControllerwydarzenia w cyklu życia prawie na pewno zrujnuje Twój tydzień. Więcej do czytania: subiektywne
-obiektywne-c.blogspot.com/2011/04/...
1
Wprowadzanie removeObserverpołączeń viewWillDisappearzgodnie ze wskazówkami jest zdecydowanie właściwą drogą, jeśli kontroler jest prezentowany przez pushViewController. Jeśli dealloczamiast tego umieścisz je dealloc, nigdy nie zostaną wezwane - przynajmniej z mojego doświadczenia ...
Christopher King
38

Od iOS 9 nie trzeba już usuwać obserwatorów.

W OS X 10.11 i iOS 9.0 NSNotificationCenter i NSDistributedNotificationCenter nie będą już wysyłać powiadomień do zarejestrowanych obserwatorów, którzy mogą zostać zwolnieni.

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

Sebastian
źródło
2
Być może nie wyślą wiadomości do obserwatorów, ale wierzę, że będą do nich mocno odnosić się, jak rozumiem. W takim przypadku wszyscy obserwatorzy pozostaną w pamięci i utworzą wyciek. Popraw mnie, jeśli się mylę.
jodła
6
Dołączona dokumentacja zawiera szczegółowe informacje na ten temat. TL; DR: to słabe odniesienie.
Sebastian
ale oczywiście jest to nadal konieczne, jeśli trzymasz obiekt, do którego się odwołujesz, i po prostu nie chcesz już słuchać powiadomień
TheEye
25

Jeśli obserwator jest dodany do kontrolera widoku , zdecydowanie polecam dodanie go viewWillAppeari usunięcie go viewWillDisappear.

RickiG
źródło
Jestem ciekawy, @RickiG: dlaczego polecasz używanie viewWillAppeari viewWillDisappeardla ViewControllers?
Izaak Overacker
2
@IsaacOveracker z kilku powodów: Twój kod konfiguracji (np. LoadView i viewDidLoad) może potencjalnie spowodować uruchomienie powiadomień, a kontroler musi to odzwierciedlić, zanim się pokaże. Jeśli zrobisz to w ten sposób, istnieje kilka korzyści. W tej chwili zdecydowałeś się "opuścić" kontroler nie przejmujesz się powiadomieniami i nie spowodują one zrobienia logiki podczas wypychania kontrolera z ekranu itp. Istnieją szczególne przypadki, w których kontroler powinien otrzymywać powiadomienia, gdy jest poza ekranem, myślę, że nie możesz tego zrobić. Ale takie wydarzenia powinny prawdopodobnie znaleźć się w Twoim modelu.
RickiG
1
@IsaacOveracker również z ARC byłoby dziwne wdrożenie dealloc, aby anulować subskrypcję powiadomień.
RickiG
4
Spośród tych, które wypróbowałem, z iOS7 jest to najlepszy sposób na rejestrowanie / usuwanie obserwatorów podczas pracy z UIViewControllers. Jedyny haczyk polega na tym, że w wielu przypadkach nie chcesz, aby obserwator był usuwany podczas używania UINavigationController i umieszczania innego UIViewController na stosie. Rozwiązanie: Możesz sprawdzić, czy VC pojawia się w viewWillDisappear, wywołując [self isBeingDismissed].
lekksi
Zdejmowanie kontrolera widoku z kontrolera nawigacji może nie powodować deallocnatychmiastowego wywołania. Powrót do kontrolera widoku może następnie spowodować wiele powiadomień, jeśli obserwator zostanie dodany w poleceniach inicjalizacji.
Jonathan Lin
20
-(void) dealloc {
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      [super dealloc];
}
Legolas
źródło
4
Odwróciłbym kolejność tych instrukcji ... Używanie selfafter [super dealloc]mnie denerwuje ... (nawet jeśli jest mało prawdopodobne, że odbiornik faktycznie wyłuskuje wskaźnik w jakikolwiek sposób, cóż, nigdy nie wiesz, jak zaimplementowały NSNotificationCenter)
Dirk
Hm. to zadziałało dla mnie. Czy zauważyłeś jakieś nietypowe zachowanie?
Legolas
1
Dirk ma rację - to nieprawda. [super dealloc]musi być zawsze ostatnim stwierdzeniem Twojej deallocmetody. Niszczy twój przedmiot; po uruchomieniu nie masz selfjuż ważnego . / cc @Dirk
jscs
38
Jeśli używasz ARC na iOS 5+, myślę, że [super dealloc]nie jest już potrzebne
pixelfreak
3
@pixelfreak silniejszy, w ARC nie można dzwonić [super dealloc]
tapmonkey
8

Generalnie umieściłem to w deallocmetodzie.

Raphael Petegrosso
źródło
7

W szybkim użyciu deinit, ponieważ dealloc jest niedostępny:

deinit {
    ...
}

Szybka dokumentacja:

Deinitializer jest wywoływany bezpośrednio przed cofnięciem przydziału instancji klasy. Piszesz deinicjalizatory za pomocą słowa kluczowego deinit, podobnie jak w przypadku pisania intializatorów za pomocą słowa kluczowego init. Deinicjatory są dostępne tylko dla typów klas.

Zazwyczaj nie ma potrzeby ręcznego czyszczenia, gdy Twoje instancje są zwalniane. Jednak podczas pracy z własnymi zasobami może być konieczne samodzielne wykonanie dodatkowych czynności porządkowych. Na przykład, jeśli utworzysz klasę niestandardową w celu otwarcia pliku i zapisania w nim danych, może być konieczne zamknięcie pliku przed zwolnieniem instancji klasy.

Morten Holmgaard
źródło
5

* edycja: ta rada dotyczy iOS <= 5 (nawet tam powinieneś dodawać viewWillAppeari usuwać viewWillDisappear- jednak rada ma zastosowanie, jeśli z jakiegoś powodu dodałeś obserwatora viewDidLoad)

Jeśli dodałeś obserwatora w viewDidLoad, powinieneś usunąć go z obu dealloci viewDidUnload. W przeciwnym razie dodasz go dwukrotnie, gdy viewDidLoadzostanie wywołany po viewDidUnload(stanie się to po ostrzeżeniu dotyczącym pamięci). Nie jest to konieczne w iOS 6, gdzie viewDidUnloadjest przestarzałe i nie zostanie wywołane (ponieważ widoki nie są już automatycznie zwalniane).

Ehren
źródło
2
Witamy w StackOverflow. Sprawdź FAQ MarkDown (ikona znaku zapytania obok pola edycji pytania / odpowiedzi). Użycie Markdwon poprawi użyteczność Twojej odpowiedzi.
marko,
5

Moim zdaniem poniższy kod nie ma sensu w ARC :

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

W iOS 6 również nie ma sensu usuwać obserwatorów viewDidUnload, ponieważ jest teraz przestarzały.

Podsumowując, zawsze to robię viewDidDisappear. Zależy to jednak również od twoich wymagań, tak jak powiedział @Dirk.

kimimaro
źródło
Wiele osób nadal pisze kod dla starszych wersji iOS niż iOS6 .... :-)
lnafziger
W ARC możesz użyć tego kodu, ale bez linii [super dealloc]; Możesz zobaczyć więcej tutaj: developer.apple.com/library/ios/#releasenotes/ObjectiveC/…
Alex
1
Co by było, gdybyś miał zwykłego NSObject, który byłby obserwatorem powiadomienia? Czy użyłbyś dealloc w tym przypadku?
qix
4

Myślę, że znalazłem wiarygodną odpowiedź ! Musiałem, ponieważ powyższe odpowiedzi są niejednoznaczne i wydają się sprzeczne. Przejrzałem książki kucharskie i przewodniki programowania.

Po pierwsze, styl addObserver: in viewWillAppear:i removeObserver:in viewWillDisappear:nie działa dla mnie (przetestowałem go), ponieważ wysyłam powiadomienie w kontrolerze widoku podrzędnego, aby wykonać kod w kontrolerze widoku nadrzędnego. Użyłbym tego stylu tylko wtedy, gdybym publikował i nasłuchiwał powiadomienia w tym samym kontrolerze widoku.

Odpowiedź, na której będę polegać najbardziej, znalazłem w iOS Programming: Big Nerd Ranch Guide 4. Ufam chłopakom z BNR, ponieważ mają centra szkoleniowe iOS i nie piszą tylko kolejnej książki kucharskiej. Dokładność prawdopodobnie leży w ich najlepszym interesie.

Przykład pierwszy BNR: addObserver: in init:, removeObserver:indealloc:

BNR, przykład drugi: addObserver: in awakeFromNib:, removeObserver:indealloc:

… Po usunięciu obserwatora dealloc:nie używają[super dealloc];

Mam nadzieję, że pomoże to następnej osobie…

Aktualizuję ten post, ponieważ Apple prawie całkowicie odszedł od Storyboardów, więc powyższe może nie dotyczyć wszystkich sytuacji. Ważną rzeczą (i powodem, dla którego dodałem ten post w pierwszej kolejności) jest zwracanie uwagi, jeśli ktoś viewWillDisappear:dzwoni. To nie dla mnie, gdy aplikacja weszła w tło.

Murat Zazi
źródło
Trudno powiedzieć, czy to prawda, ponieważ kontekst jest ważny. Wspomniano o tym już kilka razy, ale dealloc nie ma większego sensu w kontekście ARC (który jest obecnie jedynym kontekstem). Nie jest również przewidywalne, gdy wywoływana jest dealloc - łatwiej jest kontrolować funkcję viewWillDisappear. Na marginesie: jeśli Twoje dziecko musi coś przekazać rodzicowi, wzór delegata brzmi jak lepszy wybór.
RickiG,
2

Zaakceptowana odpowiedź nie jest bezpieczna i może spowodować wyciek pamięci. Proszę zostawić wyrejestrowanie w dealloc, ale także wyrejestrowanie w widoku Zniknie (to jest oczywiście, jeśli zarejestrujesz się w viewWillAppear) .... TO CO ZROBIŁEM I DZIAŁA WSPANIAŁE! :)

MobileMon
źródło
1
Zgadzam się z tą odpowiedzią. Ostrzeżenia dotyczące pamięci i wycieki powodują awarie po intensywnym korzystaniu z aplikacji, jeśli nie usunę obserwatorów w widoku WillDisappear.
SarpErdag,
2

Należy również to zauważyć viewWillDisappear jest to wywoływane również wtedy, gdy kontroler widoku przedstawia nowy UIView. Ten delegat po prostu wskazuje, że główny widok kontrolera widoku nie jest widoczny na wyświetlaczu.

W takim przypadku cofnięcie przydziału powiadomienia w viewWillDisappear może być niewygodne, jeśli używamy powiadomienia, aby umożliwić komunikację interfejsu użytkownika z nadrzędnym kontrolerem widoku.

Jako rozwiązanie zwykle usuwam obserwatora jedną z tych dwóch metod:

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

Z podobnych powodów przy pierwszym zgłoszeniu muszę liczyć się z tym, że ilekroć widok z pojawi się nad kontrolerem viewWillAppearto odpalana jest metoda. To z kolei spowoduje wygenerowanie wielu kopii tego samego powiadomienia. Ponieważ nie ma sposobu, aby sprawdzić, czy powiadomienie jest już aktywne, rozwiązuję problem, usuwając powiadomienie przed dodaniem:

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

}
Alex
źródło
-1

SWIFT 3

Istnieją dwa przypadki użycia powiadomień: - są potrzebne tylko wtedy, gdy kontroler widoku jest na ekranie; - są potrzebne zawsze, nawet jeśli użytkownik otworzył inny ekran nad prądem.

W pierwszym przypadku poprawne miejsce do dodania i usunięcia obserwatora to:

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

w drugim przypadku prawidłowy sposób to:

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

I nigdy nie umieścić removeObserverw deinit{ ... }- to błąd!

Alexander Volkov
źródło
-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)
}
urvashi bhagat
źródło