Objective-C: Onde remover o observador para NSNotification?

102

Eu tenho uma classe C objetiva. Nele, criei um método init e configurei um NSNotification nele

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

Onde eu defino o [[NSNotificationCenter defaultCenter] removeObserver:self]nesta classe? Eu sei que para a UIViewController, posso adicioná-la ao viewDidUnloadmétodo. Então, o que precisa ser feito se eu acabo de criar uma classe c objetiva?

Zhen
fonte
Eu coloquei no método dealloc.
onnoweb
1
O método dealloc não foi criado automaticamente para mim quando criei a classe C objetiva, portanto, posso adicioná-lo?
Zhen
Sim, você pode implementar -(void)dealloce adicionar removeObserser:self. Esta é a forma mais recomendada de colocarremoveObservers:self
petershine
Ainda está tudo bem para colocar o deallocmétodo no iOS 6?
wcochran de
2
Sim, não há problema em usar dealloc em projetos ARC, desde que você não chame [super dealloc] (você receberá um erro do compilador se chamar [super dealloc]). E sim, você pode definitivamente colocar seu removeObserver em desalocação.
Phil

Respostas:

112

A resposta genérica seria "assim que você não precisar mais das notificações". Obviamente, esta não é uma resposta satisfatória.

Eu recomendo que você adicione um [notificationCenter removeObserver: self]método deallocde chamada dessas classes, que você pretende usar como observadores, pois é a última chance de cancelar o registro de um observador de forma limpa. Isso, no entanto, apenas o protegerá contra travamentos devido ao centro de notificação notificando objetos mortos. Ele não pode proteger seu código contra o recebimento de notificações, quando seus objetos ainda não estão / não estão mais em um estado em que possam lidar adequadamente com a notificação. Para isso ... Veja acima.

Editar (já que a resposta parece atrair mais comentários do que eu pensava) Tudo o que estou tentando dizer aqui é: é realmente difícil dar um conselho geral sobre quando é melhor remover o observador do centro de notificação, porque isso depende:

  • No seu caso de uso (quais notificações são observadas? Quando elas são enviadas?)
  • A implementação do observador (quando está pronto para receber notificações? Quando não está mais pronto?)
  • O tempo de vida pretendido do observador (está vinculado a algum outro objeto, digamos, uma visualização ou controlador de visualização?)
  • ...

Portanto, o melhor conselho geral que posso dar: para proteger seu aplicativo. contra pelo menos uma possível falha, faça a removeObserver:dança dealloc, já que esse é o último ponto (na vida do objeto), onde você pode fazer isso de forma limpa. O que isso não significa é: "adie a remoção até que deallocseja chamado, e tudo ficará bem". Em vez disso, remova o observador assim que o objeto não estiver mais pronto (ou necessário) para receber notificações . Esse é o momento certo. Infelizmente, por não saber as respostas a nenhuma das perguntas mencionadas acima, não posso nem imaginar quando seria esse momento.

Você sempre pode proteger removeObserver:um objeto várias vezes (e todas, exceto a primeira chamada com um determinado observador, serão nops). Então: pense em fazer (de novo) deallocapenas para ter certeza, mas antes de mais nada: faça no momento apropriado (que é determinado pelo seu caso de uso).

Dirk
fonte
4
Isso não é seguro com o ARC e pode causar um vazamento. Veja esta discussão: cocoabuilder.com/archive/cocoa/311831-arc-and-dealloc.html
MobileMon
3
@MobileMon O artigo ao qual você criou um link parece confirmar meu ponto de vista. O que estou perdendo ?
Dirk de
Suponho que deva ser observado que deve-se remover o observador em outro lugar que não seja desalocar. Por exemplo, viewwilldisappear
MobileMon
1
@MobileMon - sim. Espero que seja esse o ponto que estou transmitindo com a minha resposta. Remover o observador deallocé apenas a última linha de defesa contra travar o aplicativo devido a um acesso posterior a um objeto descalocado. Mas o lugar adequado para cancelar o registro de um observador é geralmente em outro lugar (e muitas vezes, muito mais cedo no ciclo de vida do objeto). Não estou tentando dizer aqui "Ei, apenas faça isso dealloce tudo ficará bem".
Dirk
@MobileMon "Por exemplo, viewWillDisappear" O problema em dar um conselho concreto é que realmente depende de que tipo de objeto você registra como observador para que tipo de evento. Ele pode ser a solução certa para cancelar o registro um observador em viewWillDisappear(ou viewDidUnload) para UIViewControllers, mas isso realmente depende do caso de uso.
Dirk
39

Nota: Isso foi testado e funcionando 100% por cento

Rápido

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

Objective-C

Em iOS 6.0 > version, é melhor remover o observador em viewWillDisappearporque o viewDidUnloadmétodo está obsoleto.

 [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];

Muitas vezes é melhor remove observerquando a visão é removida do 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
fonte
8
Exceto que um controlador ainda pode querer notificações quando sua visualização não estiver sendo exibida (por exemplo, para recarregar uma tableView).
wcochran de
2
@wcochran recarregar / atualizar automaticamenteviewWillAppear:
Richard
@Prince você pode explicar por que viewWillDisapper é melhor do que dealloc? então adicionamos observador a self, então quando o self for retirado da memória, ele chamará desalocador e todos os observadores serão deletados, esta não é uma boa lógica.
Matrosov Alexander
Ligar removeObserver:selfpara qualquer um dos UIViewControllereventos do ciclo de vida quase certamente arruinará sua semana. Mais leitura: subjetivo-objetivo-c.blogspot.com/2011/04/…
cbowns
1
Colocar as removeObserverchamadas viewWillDisappearconforme indicado é definitivamente o caminho certo se o controlador estiver sendo apresentado via pushViewController. Se você os colocar, deallocentão deallocnunca será chamado - na minha experiência, pelo menos ...
Christopher King
38

Desde o iOS 9 não é mais necessário remover observadores.

No OS X 10.11 e iOS 9.0, NSNotificationCenter e NSDistributedNotificationCenter não enviará mais notificações para observadores registrados que podem ser desalocados.

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

Sebastian
fonte
2
Talvez eles não enviem mensagens aos observadores, mas acredito que manterão uma forte referência a eles, pelo que entendi. Nesse caso, todos os observadores ficarão na memória e produzirão um vazamento. Corrija-me se eu estiver errado.
fir
6
A documentação vinculada entra em detalhes sobre isso. TL; DR: é uma referência fraca.
Sebastian
mas é claro que ainda é necessário caso você mantenha o objeto referenciando-os por perto e simplesmente não queira mais ouvir as notificações
TheEye
25

Se o observador for adicionado a um controlador de visualização , recomendo fortemente adicioná-lo viewWillAppeare removê-lo viewWillDisappear.

RickiG
fonte
Estou curioso, @RickiG: por que você recomenda usar viewWillAppeare viewWillDisappearpara viewControllers?
Isaac Overacker
2
@IsaacOveracker alguns motivos: seu código de configuração (por exemplo, loadView e viewDidLoad) pode causar o disparo das notificações e seu controlador precisa refletir isso antes de aparecer. Se você fizer assim, existem alguns benefícios. No momento em que você decidiu "deixar" o controlador, você não se preocupa com as notificações e elas não farão com que você faça lógica enquanto o controlador estiver sendo empurrado para fora da tela, etc. Existem casos especiais em que o controlador deve receber notificações quando estiver fora da tela, acho que você não pode fazer isso. Mas eventos como esse provavelmente deveriam estar em seu modelo.
RickiG
1
@IsaacOveracker também com ARC, seria estranho implementar desalocar para cancelar a assinatura de notificações.
RickiG
4
Dos que tentei, com iOS7 esta é a melhor maneira de registrar / remover observadores ao trabalhar com UIViewControllers. O único problema é que, em muitos casos, você não quer que o observador seja removido ao usar UINavigationController e enviar outro UIViewController para a pilha. Solução: você pode verificar se o VC está sendo exibido em viewWillDisappear chamando [self isBeingDismissed].
lekksi
Tirar o controlador de visualização do controlador de navegação pode não fazer dealloccom que seja chamado imediatamente. Voltar para o controlador de visualização pode causar várias notificações se o observador for adicionado aos comandos de inicialização.
Jonathan Lin
20
-(void) dealloc {
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      [super dealloc];
}
Legolas
fonte
4
Eu mudaria a ordem dessas instruções ... Usar selfdepois [super dealloc]me deixa nervoso ... (mesmo que seja improvável que o receptor cancele a referência do ponteiro de alguma forma, bem, nunca se sabe como eles foram implementados NSNotificationCenter)
Dirk
Hm. Ele tem trabalhado para mim. Você notou algum comportamento incomum?
Legolas
1
Dirk está certo - isso é incorreto. [super dealloc]deve ser sempre a última afirmação do seu deallocmétodo. Ele destrói seu objeto; depois de executado, você não tem mais um válido self. / cc @Dirk
jscs
38
Se estiver usando ARC no iOS 5+, acho que [super dealloc]não é mais necessário
pixelfreak
3
@pixelfreak mais forte, não é permitido pelo ARC chamar [super dealloc]
tapmonkey
8

Em geral, eu coloco no deallocmétodo.

Raphael Petegrosso
fonte
7

Em swift, use deinit porque o dealloc não está disponível:

deinit {
    ...
}

Documentação do Swift:

Um desinicializador é chamado imediatamente antes de uma instância de classe ser desalocada. Você escreve deinitializers com a palavra-chave deinit, semelhante a como os inicializadores são escritos com a palavra-chave init. Os desinicializadores estão disponíveis apenas em tipos de classe.

Normalmente, você não precisa realizar uma limpeza manual quando suas instâncias são desalocadas. No entanto, quando estiver trabalhando com seus próprios recursos, pode ser necessário realizar alguma limpeza adicional. Por exemplo, se você criar uma classe personalizada para abrir um arquivo e gravar alguns dados nele, pode ser necessário fechar o arquivo antes que a instância da classe seja desalocada.

Morten Holmgaard
fonte
5

* editar: Este conselho se aplica ao iOS <= 5 (mesmo lá você deve adicionar viewWillAppeare remover viewWillDisappear- no entanto, o conselho se aplica se por algum motivo você adicionou o observador emviewDidLoad )

Se você adicionou o observador em, viewDidLoaddeve removê-lo em ambos dealloce viewDidUnload. Caso contrário, você acabará adicionando-o duas vezes quando viewDidLoadfor chamado depois viewDidUnload(isso acontecerá após um aviso de memória). Isso não é necessário no iOS 6, onde viewDidUnloadestá obsoleto e não será chamado (porque as visualizações não são mais descarregadas automaticamente).

Ehren
fonte
2
Bem-vindo ao StackOverflow. Verifique as perguntas frequentes do MarkDown (ícone de ponto de interrogação ao lado da caixa de edição de pergunta / resposta). Usar o Markdwon melhorará a usabilidade de sua resposta.
março de
5

Na minha opinião, o código a seguir não faz sentido no ARC :

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

No iOS 6 , também não há sentido em remover observadores viewDidUnload, porque ele está obsoleto agora.

Para resumir, sempre faço isso em viewDidDisappear. Porém, também depende de seus requisitos, assim como disse @Dirk.

Kimimaro
fonte
Muitas pessoas ainda estão escrevendo código para versões mais antigas do iOS do que iOS6 .... :-)
lnafziger
No ARC, você pode usar este código, mas sem a linha [super dealloc]; Você pode ver mais aqui: developer.apple.com/library/ios/#releasenotes/ObjectiveC/…
Alex
1
E se você tivesse um NSObject regular como observador de uma notificação? Você usaria dealloc neste caso?
qix
4

Acho que encontrei uma resposta confiável ! Tive de fazê-lo, pois as respostas acima são ambíguas e parecem contraditórias. Procurei livros de receitas e guias de programação.

Primeiro, o estilo de addObserver:em viewWillAppear:e removeObserver:emviewWillDisappear: não funciona para mim (eu testei) porque estou postando uma notificação em um controlador de visão filho para executar código no controlador de visão pai. Eu só usaria esse estilo se estivesse postando e ouvindo a notificação no mesmo controlador de visualização.

A resposta na qual mais confiarei, encontrei em iOS Programming: Big Nerd Ranch Guide 4. Eu confio nos caras do BNR porque eles têm centros de treinamento iOS e eles não estão apenas escrevendo outro livro de receitas. É provavelmente do seu interesse serem precisos.

BNR exemplo um: addObserver:em init:, removeObserver:emdealloc:

Exemplo dois de BNR: addObserver:em awakeFromNib:, removeObserver:emdealloc:

... ao remover o observador em dealloc:eles não usam[super dealloc];

Espero que isso ajude a próxima pessoa ...

Estou atualizando este post porque a Apple agora quase adotou completamente os Storyboards, então o mencionado acima pode não se aplicar a todas as situações. O importante (e a razão pela qual adicionei este post em primeiro lugar) é prestar atenção se você viewWillDisappear:for chamado. Não era para mim quando o aplicativo entrou em segundo plano.

Murat Zazi
fonte
É difícil dizer se isso está certo, pois o contexto é importante. Já é mencionado algumas vezes, mas desalocar faz pouco sentido em um contexto ARC (que é o único contexto agora). Também não é previsível quando desalocar é chamado - viewWillDisappear é mais fácil de controlar. Uma observação lateral: se seu filho precisa comunicar algo aos pais, o padrão de delegado parece uma escolha melhor.
RickiG
2

A resposta aceita não é segura e pode causar vazamento de memória. Por favor, deixe o cancelamento de registro em dealloc, mas também cancele o registro em viewWillDisappear (isto é, claro, se você se registrar em viewWillAppear) .... ISSO É O QUE EU FIZ DE QUALQUER MANEIRA E FUNCIONOU BEM! :)

MobileMon
fonte
1
Eu concordo com esta resposta. Eu experimento avisos de memória e vazamentos que levam a travamentos após o uso intensivo do aplicativo se eu não remover os observadores em viewWillDisappear.
SarpErdag
2

É importante notar também que viewWillDisappearé chamado também quando o controlador de visualização apresenta um novo UIView. Esse delegado simplesmente indica que a visualização principal do controlador de visualização não está visível na tela.

Nesse caso, desalocar a notificação em viewWillDisappearpode ser inconveniente se estivermos usando a notificação para permitir que o UIview se comunique com o controlador de visualização pai.

Como solução, geralmente removo o observador em um destes dois métodos:

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

Por razões semelhantes, quando eu emito a notificação pela primeira vez, preciso levar em consideração o fato de que sempre que uma exibição aparece acima do controlador, o viewWillAppearmétodo é disparado. Isso, por sua vez, irá gerar várias cópias da mesma notificação. Como não há uma maneira de verificar se uma notificação já está ativa, elimino o problema removendo a notificação antes de adicioná-la:

- (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
fonte
-1

SWIFT 3

Existem dois casos de uso de notificações: - eles são necessários apenas quando o controlador de visualização está na tela; - eles são necessários sempre, mesmo se o usuário abrir outra tela ao longo da corrente.

Para o primeiro caso, o local correto para adicionar e remover o observador é:

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

para o segundo caso, a maneira correta é:

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

E nunca colocar removeObserverem deinit{ ... }- é um erro!

Alexander Volkov
fonte
-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
fonte