Objective-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类,该怎么办?


我把它放在dealloc方法中。
onnoweb 2011年

1
创建目标c类时,不会自动为我创建dealloc方法,因此可以添加它吗?

是的,您可以实施-(void)dealloc然后添加removeObserser:self它。这是最推荐的放置方式removeObservers:self
petershine

dealloc在iOS 6中放入方法仍然可以吗?
wcochran

2
是的,只要不调用[super dealloc],就可以在ARC项目中使用dealloc(如果调用[super dealloc],则会出现编译器错误)。是的,您绝对可以将removeObserver放入dealloc中。
菲尔,

Answers:


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)2013年

我想应该指出的是,除了dealloc之外,还应该删除观察者。例如,viewwillappearing
MobileMon

1
@MobileMon-是的 我希望这就是我要回答的重点。将观察者dealloc移入只是防止由于以后访问未分配的对象而导致应用程序崩溃的最后一道防线。但是注销观察者的合适位置通常在其他地方(通常在对象生命周期的更早阶段)。我在这里不是要说“嘿,只要进去dealloc,一切都会好起来的”。
德克(Dirk)

@MobileMon“例如,viewWillDisappear”提供具体建议的问题在于,它实际上取决于您为观察者注册的事件类型。这可能是注销以观察员的解决方案viewWillDisappear(或viewDidUnload为)UIViewControllerS,但实际上取决于使用情况。
德克(Dirk)

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

PresentedViewController

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,它能够更好地在去除观察者viewWillDisappearviewDidUnload方法已经过时了。

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

PresentedViewController

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

8
除了控制器可能不希望在其视图显示时发出通知(例如重新加载tableView)。
wcochran

2
@wcochran自动重新加载/刷新viewWillAppear:
理查德(Richard)

@Prince可以解释为什么viewWillDisapper比dealloc更好吗?因此我们在self上添加了观察者,因此当self从内存中删除时,它将调用dealloc然后删除所有观察者,这不是一个好的逻辑。
Matrosov Alexander 2014年

几乎可以保证调用removeObserver:self任何UIViewController生命周期事件都会破坏您的一周。更多阅读:subjective-objective-c.blogspot.com/2011/04/...
cbowns

1
如果通过来显示控制器,则按照指示进行removeObserver呼叫viewWillDisappear绝对是正确的方法pushViewController。如果您把它们放进去,dealloc那么dealloc就永远不会被召唤-至少以我的经验来说……
Christopher King

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:为什么建议您为ViewController 使用viewWillAppearviewWillDisappear
艾萨克·奥弗雷克

2
@IsaacOveracker有几个原因:您的设置代码(例如loadView和viewDidLoad)可能会导致触发通知,并且您的控制器需要在显示之前进行反映。如果您这样做,则有一些好处。目前,您决定“离开”您不关心通知的控制器,当控制器被推离屏幕时,它们不会导致您执行逻辑操作。在某些特殊情况下,控制器应在收到通知时接收通知屏幕外,我想你不能这样做。但是类似的事件可能应该在您的模型中。
RickiG 2012年

1
@IsaacOveracker也与ARC一起实现dealloc以取消订阅通知是很奇怪的。
RickiG 2012年

4
在我尝试过的那些方法中,对于iOS7,这是使用UIViewControllers时注册/删除观察者的最佳方法。唯一的问题是,在许多情况下,您不希望在使用UINavigationController并将另一个UIViewController推入堆栈时删除观察者。解决方案:您可以通过调用[self isBeingDismissed]来检查VC是否在viewWillDisappear中弹出。
lekksi 2014年

从导航控制器弹出视图控制器可能不会导致dealloc立即被调用。如果在初始化命令中添加了观察者,则返回视图控制器可能会导致多个通知。
乔纳森·林

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

4
我会改变这些指令的顺序...使用selfafter [super dealloc]会让我感到紧张...(即使接收器不太可能以任何方式实际取消引用指针,嗯,你永远都不知道它们是如何实现的NSNotificationCenter
Dirk

嗯 它为我工作。您是否注意到任何异常行为?
Legolas

1
德克是对的-这是不正确的。[super dealloc]必须始终是方法的最后一个语句dealloc。它摧毁了你的物体;它运行后,您将不再拥有有效的密码self。/ cc @Dirk
jscs 2011年

38
如果在iOS 5+上使用ARC,我认为[super dealloc]不再需要
pixelfreak 2012年

3
@pixelfreak更强,在ARC下不允许调用[super dealloc]
tapmonkey


7

快速使用deinit是因为dealloc不可用:

deinit {
    ...
}

Swift文档:

在取消释放类实例之前,将调用反初始化程序。您可以使用deinit关键字编写反初始化器,类似于使用init关键字编写初始化器的方式。反初始化器仅在类类型上可用。

通常,在释放实例后,您无需执行手动清理。但是,当您使用自己的资源时,可能需要自己进行一些额外的清理。例如,如果创建一个自定义类来打开文件并向其中写入一些数据,则可能需要在释放该类实例之前关闭该文件。


5

* edit:此建议适用于<= 5的iOS(即使您应该在其中添加viewWillAppear和删除viewWillDisappear-但是,如果出于某种原因您已在中添加了观察者,则该建议也适用viewDidLoad

如果您在其中添加了观察者,viewDidLoad则应在dealloc和中将其删除viewDidUnload。否则,您将在两次viewDidLoad之后被调用时最终将其添加两次viewDidUnload(这将在出现内存警告之后发生)。在viewDidUnload已弃用且不会调用的iOS 6中,这不是必需的(因为不再自动卸载视图)。


2
欢迎使用StackOverflow。请检出MarkDown常见问题解答(问题/答案编辑框旁边的问号图标)。使用Markdwon将改善您答案的可用性。
marko 2012年

5

我认为,以下代码在ARC中毫无意义:

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

iOS 6中,也没有viewDidUnload必要删除中的观察者,因为它已被弃用。

总结起来,我总是在做viewDidDisappear。但是,这也取决于您的要求,就像@Dirk所说的那样。


很多人仍在为比iOS6更旧版本的iOS编写代码.... :-)
lnafziger 2013年

在ARC中,您可以使用此代码,但无需使用[super dealloc]行;你可以看到更多在这里:developer.apple.com/library/ios/#releasenotes/ObjectiveC/...
亚历

1
如果您有一个常规的NSObject成为通知的观察者怎么办?在这种情况下,您会使用dealloc吗?
2013年

4

我想我找到了可靠的答案!我不得不这样做,因为上面的答案是模棱两可的,而且似乎是矛盾的。我浏览了食谱和编程指南。

首先,addObserver:in viewWillAppear:removeObserver:in 的样式viewWillDisappear:对我不起作用(我对其进行了测试),因为我正在子视图控制器中发布通知以在父视图控制器中执行代码。仅当我在同一视图控制器中发布和侦听通知时,才使用这种样式。

我最依赖的答案是在《 iOS编程:大书呆子牧场指南》第4版中找到的。我相信BNR员工是因为他们拥有iOS培训中心,而且他们不仅在编写另一本菜谱。准确可能符合他们的最大利益。

BNR示例一:addObserver:in init:removeObserver:indealloc:

BNR示例二:addObserver:in awakeFromNib:removeObserver:indealloc:

…当删除观察者时dealloc:不使用[super dealloc];

我希望这对下一个人有帮助...

我正在更新这篇文章,因为Apple现在几乎已经完全使用了Storyboard,因此上述内容可能并不适用于所有情况。重要的事情(也是我首先添加此帖子的原因)是要注意是否接到您viewWillDisappear:的电话。当应用程序进入后台时,这不适合我。


由于上下文很重要,很难说这是否正确。它已经被提到过几次了,但是在ARC上下文中(目前是唯一的上下文),dealloc毫无意义。调用dealloc时也是不可预测的-viewWillDisappear更易于控制。旁注:如果您的孩子需要与父母沟通一些事情,委托模式听起来是一个更好的选择。
RickiG

2

接受的答案不安全,并且可能导致内存泄漏。请不要将取消注册保留在dealloc中,也要取消注册在viewWillDisappear中(当然,如果您在viewWillAppear中注册)....无论如何,这是我的最大成功!:)


1
我同意这个答案。如果不删除viewWillDisappear中的观察者,则在密集使用该应用程序后,我会遇到内存警告和泄漏,导致崩溃。
SarpErdag 2014年

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

而从来没有把removeObserverdeinit{ ... }-这是一个错误!


-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)
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.