【问题标题】:Objective-C: Where to remove observer for NSNotification?Objective-C:在哪里删除 NSNotification 的观察者?
【发布时间】:2011-09-22 02:09:34
【问题描述】:

我有一个客观的 C 类。在其中,我创建了一个 init 方法并在其中设置了一个 NSNotification

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

在这个类中我在哪里设置[[NSNotificationCenter defaultCenter] removeObserver:self]?我知道对于一个UIViewController,我可以将它添加到viewDidUnload方法中那么如果我只是创建了一个目标c类需要做什么呢?

【问题讨论】:

  • 我放在dealloc方法里。
  • dealloc方法在我创建objective c类的时候没有自动为我创建,所以我添加进去就可以了?
  • 是的,您可以实现-(void)dealloc,然后在其中添加removeObserser:self。这是最推荐的放removeObservers:self的方式
  • 在iOS 6中放入dealloc方法还可以吗?
  • 是的,在ARC项目中使用dealloc是可以的,只要不调用[super dealloc](调用[super dealloc]会出现编译错误)。是的,你绝对可以把你的 removeObserver 放在 dealloc 中。

标签: objective-c ios nsnotifications


【解决方案1】:

这是唯一正确的答案(所有其他都错误地建议使用deinitdealloc - 这是对类实例生命周期和iOS系统的明显误解。

斯威夫特 5

使用通知有两种情况:

  • 在视图控制器在屏幕上时需要它们 (viewWillAppear + viewWillDisappear)
  • 他们是 需要总是,即使用户打开了另一个屏幕(viewDidLoad + viewWillDisappear 带有 "if")。

对于第一种情况,添加和删除观察者的正确位置是:

    /// Add observers
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        NotificationCenter.default.addObserver(...)
    }

    /// Remove observers
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

// remove when screen dismissed
        NotificationCenter.default.removeObserver(self) 
    }

对于第二种情况,正确的方法是:

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

    /// Remove observers
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

// remove only when view controller is removed disappear forever
        if self.isBeingDismissed 
        || !(self.navigationController?.viewControllers.contains(self) ?? true) {
            NotificationCenter.default.removeObserver(self)
        }
    }

永远不要将removeObserver 放入deinit{ ... }dealloc - 这是一个错误!

【讨论】:

    【解决方案2】:

    注意:这已经过测试并且可以 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 中,最好删除 viewWillDisappear 中的观察者,因为不推荐使用 viewDidUnload 方法。

     [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];
    

    当视图从navigation stack or hierarchy 中删除时,remove observer 的效果要好很多倍。

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

    【讨论】:

    • 除非控制器在其视图显示时仍需要通知(例如,重新加载 tableView)。
    • @wcochran 自动重新加载/刷新viewWillAppear:
    • @Prince 你能解释一下为什么 viewWillDisapper 比 dealloc 更好吗?所以我们给self添加了观察者,所以当self从内存中删除时,它会调用dealloc,然后所有的观察者都将被删除,这不是一个好的逻辑吗?
    • 在任何UIViewController 生命周期事件中调用removeObserver:self 几乎肯定会毁了你的一周。更多阅读:subjective-objective-c.blogspot.com/2011/04/…
    • 如果控制器是通过pushViewController 呈现的,那么按照指示将removeObserver 调用放入viewWillDisappear 绝对是正确的方法。如果你把它们放在dealloc 中,那么dealloc 将永远不会被调用——至少根据我的经验......
    【解决方案3】:
    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)
    }
    

    【讨论】:

      【解决方案4】:

      还要注意viewWillDisappear 在视图控制器呈现新的 UIView 时也会被调用,这一点很重要。这个委托只是表明视图控制器主视图在显示器上不可见。

      在这种情况下,如果我们使用通知来允许 UIview 与父视图控制器通信,那么在 viewWillDisappear 中释放通知可能会很不方便。

      作为一种解决方案,我通常使用以下两种方法之一移除观察者:

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

      【讨论】:

        【解决方案5】:

        从 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

        【讨论】:

        • 可能他们不会向观察者发送消息,但我相信他们会按照我的理解保持对他们的强烈引用。在这种情况下,所有观察者都将留在内存中并产生泄漏。如果我错了,请纠正我。
        • 链接的文档对此进行了详细介绍。 TL;DR:这是一个弱参考。
        • 但当然,如果您保留引用它们的对象并且不想再收听通知,这仍然是必要的
        【解决方案6】:

        如果将观察者添加到 视图控制器,我强烈建议将其添加到 viewWillAppear 并在 viewWillDisappear 中删除。

        【讨论】:

        • 我很好奇,@RickiG:你为什么推荐使用 viewWillAppearviewWillDisappear 作为 viewControllers?
        • @IsaacOveracker 有几个原因:您的设置代码(例如 loadView 和 viewDidLoad)可能会导致通知被触发,并且您的控制器需要在它显示之前反映它。如果你这样做,会有一些好处。在您决定“离开”控制器时,您并不关心通知,并且它们不会导致您在控制器被推离屏幕等时执行逻辑。在某些特殊情况下,控制器应该在接收通知时收到通知离屏我猜你不能这样做。但是这样的事件可能应该在您的模型中。
        • @IsaacOveracker 也与 ARC 一起使用 dealloc 来取消订阅通知会很奇怪。
        • 在我尝试过的那些中,对于 iOS7,这是在使用 UIViewControllers 时注册/删除观察者的最佳方式。唯一的问题是,在许多情况下,您不希望在使用 UINavigationController 并将另一个 UIViewController 推送到堆栈时删除观察者。解决方法:可以通过调用[self isBeingDismissed]来查看viewWillDisappear中是否弹出VC。
        • 从导航控制器弹出视图控制器可能不会导致立即调用dealloc。如果在初始化命令中添加了观察者,则返回视图控制器可能会导致多个通知。
        【解决方案7】:

        在我看来,以下代码在 ARC 中没有意义:

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

        iOS 6 中,删除 viewDidUnload 中的观察者也没有任何意义,因为它现在已被弃用。

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

        【讨论】:

        • 很多人仍在为比 iOS6 更早的 iOS 版本编写代码.... :-)
        • 在 ARC 中,您可以使用此代码但没有 [super dealloc]; 行你可以在这里看到更多:developer.apple.com/library/ios/#releasenotes/ObjectiveC/…
        • 如果你有一个普通的 NSObject 作为通知的观察者呢?在这种情况下你会使用 dealloc 吗?
        【解决方案8】:

        *edit:此建议适用于 iOS viewWillAppear 中添加并在 viewWillDisappear 中删除 - 但是如果出于某种原因您在 viewDidLoad 中添加了观察者,则该建议适用)

        如果您在 viewDidLoad 中添加了观察者,您应该在 deallocviewDidUnload 中删除它。否则,当在viewDidUnload 之后调用viewDidLoad 时,您最终会添加两次(这将在内存警告之后发生)。在不推荐使用 viewDidUnload 并且不会被调用的 iOS 6 中,这不是必需的(因为不再自动卸载视图)。

        【讨论】:

        • 欢迎来到 StackOverflow。请查看 MarkDown 常见问题解答(问题/答案编辑框旁边的问号图标)。使用 Markdwon 将提高您的答案的可用性。
        【解决方案9】:

        在 swift 中使用 deinit,因为 dealloc 不可用:

        deinit {
            ...
        }
        

        Swift 文档:

        在类实例被调用之前立即调用反初始化器 解除分配。您使用 deinit 关键字编写反初始化器,类似 了解如何使用 init 关键字编写初始化程序。反初始化器 仅适用于类类型。

        通常情况下,您不需要执行手动清理 实例被释放。但是,当您使用自己的 资源,您可能需要执行一些额外的清理 你自己。例如,如果您创建一个自定义类来打开一个文件并 向其中写入一些数据,您可能需要在 类实例被释放。

        【讨论】:

        • 错了!你永远不应该取消订阅deinit,因为它没用。当实例从内存中清除时,iOS会自动取消订阅它,然后它会调用deinit,所以它没用。
        • @AlexanderVolkov 请记住这个答案是 5 岁,当时 swift 是全新的。当时它是解决方案,但此后发生了变化。
        • 不。如果 deinit 被调用,那么 iOS 会自动取消订阅观察者。这在 ObjC 和 Swift 中是相同的,它取决于系统,而不是语言。可能我错了,但随后提供参考文档。
        【解决方案10】:

        我想我找到了一个可靠的答案!我不得不这样做,因为上面的答案模棱两可,似乎自相矛盾。我浏览了 Cookbooks 和 Programming Guides。

        首先,viewWillAppear: 中的addObserver:viewWillDisappear: 中的removeObserver: 的样式对我不起作用(我对其进行了测试),因为我在子视图控制器中发布通知以在父视图控制器中执行代码视图控制器。如果我在同一个视图控制器中发布和收听通知,我只会使用这种风格。

        我最依赖的答案,我在 iOS Programming: Big Nerd Ranch Guide 4th 中找到的。我相信 BNR 的人,因为他们有 iOS 培训中心,而且他们不仅仅是在写另一本食谱。准确可能符合他们的最大利益。

        BNR 示例一:addObserver: in init:, removeObserver: in dealloc:

        BNR 示例二:awakeFromNib: 中的addObserver:dealloc: 中的removeObserver:

        ...当删除dealloc: 中的观察者时,他们不使用[super dealloc];

        我希望这可以帮助下一个人……

        我正在更新这篇文章,因为 Apple 现在几乎完全放弃了 Storyboard,因此上述内容可能不适用于所有情况。重要的是(以及我首先添加此帖子的原因)是注意您的 viewWillDisappear: 是否被调用。应用程序进入后台时不是给我的。

        【讨论】:

        • 很难说这是否正确,因为上下文很重要。已经提到过几次了,但是 dealloc 在 ARC 上下文中没有什么意义(这是目前唯一的上下文)。调用 dealloc 时也无法预测 - viewWillDisappear 更容易控制。附注:如果您的孩子需要与其父母交流某些内容,委托模式听起来是一个更好的选择。
        【解决方案11】:

        一般的答案是“一旦您不再需要通知”。这显然不是一个令人满意的答案。

        我建议您在这些类的 dealloc 方法中添加一个调用 [notificationCenter removeObserver: self],您打算将其用作观察者,因为这是彻底取消注册观察者的最后机会。但是,这只会保护您免受由于通知中心通知死对象而导致的崩溃。当您的对象尚未/不再处于可以正确处理通知的状态时,它不能保护您的代码不接收通知。为此...见上文。

        编辑(因为答案似乎比我想象的要吸引更多的 cmets)我在这里想说的是:很难就何时最好删除通知中心的观察者,因为这取决于:

        • 关于您的用例(观察到哪些通知?何时发送?)
        • 观察者的实现(什么时候准备好接收通知?什么时候不再准备好?)
        • 观察者的预期生命周期(它是否与其他对象相关联,例如视图或视图控制器?)
        • ...

        所以,我能提出的最佳一般建议是:保护您的应用。针对至少一个可能的失败,在dealloc 中跳removeObserver:,因为这是最后一点(在对象的生命中),你可以干净地做到这一点。这并不意味着:“只需将删除推迟到dealloc 被调用,一切都会好起来的”。相反,只要对象不再准备好(或不需要)接收通知,就移除观察者。那是完全正确的时刻。不幸的是,我不知道上述任何问题的答案,我什至无法猜测那一刻会是什么时候。

        你总是可以安全地removeObserver: 一个对象多次(除了给定观察者的第一次调用之外,所有调用都是nops)。所以:考虑在dealloc 中(再次)执行此操作,但首先是:在适当的时间执行此操作(由您的用例决定)。

        【讨论】:

        • 这对 ARC 来说是不安全的,并且可能会导致泄漏。请参阅此讨论:cocoabuilder.com/archive/cocoa/311831-arc-and-dealloc.html
        • @MobileMon 您链接到的文章似乎说明了我的观点。我错过了什么?
        • 我想应该注意的是,除了dealloc之外,应该在其他地方删除观察者。例如,viewwilldisappear
        • @MobileMon -- 是的。我希望,这就是我回答的重点。删除dealloc 中的观察者只是防止应用程序由于稍后访问已释放对象而崩溃的最后一道防线。但是注销观察者的适当位置通常在其他地方(并且通常在对象生命周期的早期)。我不想在这里说“嘿,只需在dealloc 中进行操作,一切都会好起来的”。
        • @MobileMon "例如,viewWillDisappear" 给出具体建议的问题在于,它实际上取决于您注册为观察者的对象是哪种事件。 可能viewWillDisappear(或viewDidUnload)为UIViewControllers 取消注册观察者是正确的解决方案,但这实际上取决于用例。
        【解决方案12】:

        接受的答案不安全,可能导致内存泄漏。请务必在 dealloc 中取消注册,但也要在 viewWillDisappear 中取消注册(当然,如果您在 viewWillAppear 中注册)......这就是我所做的,而且效果很好! :)

        【讨论】:

        • 我同意这个答案。如果我不删除 viewWillDisappear 中的观察者,我会在大量使用应用程序后遇到内存警告和泄漏导致崩溃。
        【解决方案13】:
        -(void) dealloc {
              [[NSNotificationCenter defaultCenter] removeObserver:self];
              [super dealloc];
        }
        

        【讨论】:

        • 我会改变这些指令的顺序......在[super dealloc]之后使用self让我感到紧张......(即使接收者不太可能以任何方式实际取消引用指针,好吧,你永远不知道,他们是如何实现的NSNotificationCenter)
        • 嗯。它对我有用。您是否注意到任何异常行为?
        • Dirk 是对的——这是不正确的。 [super dealloc] 必须始终是您的dealloc 方法的最后 语句。它会破坏你的对象;运行后,您不再拥有有效的self。 /cc @Dirk
        • 如果在 iOS 5+ 上使用 ARC,我认为不再需要 [super dealloc]
        • @pixelfreak更强,ARC下不允许调用[super dealloc]
        【解决方案14】:

        一般我把它放到dealloc方法中。

        【讨论】:

          猜你喜欢
          • 2015-04-25
          • 1970-01-01
          • 2015-10-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多