如何停止 NSNotification 中观察者被调用两次的问题?

64

我有一个 NSNotification 的观察者,但它被调用了两次。我不知道该怎么处理它。

我通过谷歌搜索了解却没有找到解决方案。

[[NSNotificationCenter defaultCenter] addObserver:self
     selector:@selector(connectedToServer:) name:@"ConnectedToServer" object:nil];

- (void)connectedToServer:(NSNotification*)notification {

    [[NSNotificationCenter defaultCenter] postNotificationName:@"SendMessageToServer" object:message];
}

1
可以提供一些代码让我们查看会有帮助。 - Totumus Maximus
请查看此答案http://stackoverflow.com/questions/10835000/initwithnibname-either-called-twice-or-wrong-xib-loaded,有关接口生成器中“蓝色框”类实例的问题... - M Jesse
也许你的viewDidLoad被调用了两次。尝试查看这个答案是否可以帮助你: https://dev59.com/XaTja4cB1Zd3GeqPINvC#46018292 - Cue
9个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
146

解决方案1: 首先检查通知本身是否被两次发布。

解决方案2: 即使通知只被发布一次,action也会根据您为通知添加观察者的次数调用相应的次数(无论通知是否相同)。例如,以下两行代码将为同一个通知(aSelector)注册观察者(self)两次。

[[NSNotificationCenter defaultCenter] addObserver:self selector:aSelector name:aName object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:aSelector name:aName object:nil];

您需要找到第二次添加观察者的位置,并将其删除。还要确保添加观察者的代码不会被调用两次。

解决方案3:如果您不确定是否已经添加了观察者,可以简单地执行以下操作。这将确保观察者仅添加一次。

[[NSNotificationCenter defaultCenter] removeObserver:self name:aName object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:aSelector name:aName object:nil];

2
也许你会收到两次通知。为了检查它,请在aSelector中设置断点并查看调用堆栈。 - brigadir
10
移除观察者模式中心的指定通知,通知名称为aName,对象为空(nil),self是当前对象。 - SamehDos
3
Azhar 一样,我无法使用上述方法解决这个问题。不确定发生了什么事情——但在我的情况下,我转而使用 delegate 来满足我的回调需求。这不是所有情况的解决方案,但可以作为一种解决方法,帮助一些未来遇到类似问题的读者。 - toblerpwn
哇,我调试了太长时间了...我在函数开头调用了addObserver,但没有正确地删除它...因此我的函数被调用了多次。在其上方添加了remove并且现在完美运行。 - RyanG
2
我从解决方案3中得到了解决方法。我已经在viewDidLoad中实现了这种方法[NSNotificationCenter defaultCenter],并且在类中调用了两次viewDidLoad。 - Bhavin Ramani
显示剩余3条评论

17

如果你的addObserver方法运行多次,它将创建多个观察者。我的问题是我不知何故在viewWillAppear中放置了我的观察者,在发布通知之前它出现了多次,这导致我的观察者被调用多次。

尽管EmptyStack的第三种解决方案可以工作,但有一个原因导致你的观察者被调用两次,因此通过找到它,你可以防止不必要的代码行而不是添加和删除相同的观察者。

我建议将你的观察者放在viewDidLoad中,以避免像我经历的这样的简单错误。


1
这可能是解决方案。可以在addObserver 方法中设置断点,以检查相同观察者已被添加了多少次。 - Ashwin G
1
如果我几个小时前读到这篇文章,我就可以节省很多时间了。我只是想不明白为什么会注册多个观察者。现在我把注册从viewWillAppear移到viewDidLoad中,它完美地工作了。非常感谢。 - Maverick1st
1
使用GCD和dispatch_once来确保观察者仅被添加一次。static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //添加观察者代码 }); - RamaKrishna Chunduri
1
在我的情况下,viewDidLoad被意外地调用了两次。不要指望viewDidLoad只会被调用一次。我的意思是我确实这样做了,但这次问题就出在这里。 - Jonny
1
请注意,关于“为什么viewDidLoad()被调用两次?”这样的问题,其他地方有很多答案-它们都强调没有保证viewDidLoad只会被调用一次。似乎在添加观察者之前执行removeObserver是正确的方法。 - bruce1337
显示剩余4条评论

6

尝试在viewWillDisappear方法中删除观察者:

-(void)viewWillDisappear:(BOOL)animated{

[[NSNotificationCenter defaultCenter] removeObserver:self name:@"startAnimating" object:nil]; }

2

对于那些寻找Swift 2.2及以上解决方案并像我一样到达这个问题的人,您可以创建以下扩展:

import Foundation

extension NSNotificationCenter {
  func setObserver(observer: AnyObject, selector: Selector, name: String?, object: AnyObject?) {
    NSNotificationCenter.defaultCenter().removeObserver(observer, name: name, object: object)
    NSNotificationCenter.defaultCenter().addObserver(observer, selector: selector, name: name, object: object)
  }
}
您可以按照以下方式调用此方法:
NSNotificationCenter.defaultCenter().setObserver(self, selector: #selector(methodName), name: "name", object: nil)

如果存在先前的观察器,该扩展将处理其删除。即使没有先前的观察器,此代码也不会崩溃。


4
如果使用这种方式,如果self(即UIViewcontroller)一再被分配,每个新的self对象都会创建一个观察者。使用上述代码只会移除当前self的观察者,而不是旧self的观察者。之前遇到过这个问题。 - Cian

2

试着在 [[NSNotificationCenter defaultCenter] addObserver:self selector:aSelector name:aName object:nil]; 上设置一个断点,并检查它是否被调用了超过一次。


0

我有两个相同类的实例,花了一些时间才意识到通知不是在该类的一个实例中运行两次,而是在两个实例中各运行一次。


0

Swift 5+ 解决方案

NotificationCenter.default.removeObserver(self, name: aName, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(aSelector), name: aName, object: nil)

一些技巧(而且“a couple”总是指2个):

  1. 选择器方法必须暴露给@objc才能工作,因此根据我们的示例:

    @objc func aSelector() { //在这里执行操作 }
    
  • 我将通知放在init()deinit()中,并使用单例来避免重复,像这样:

    init() {
        NotificationCenter.default.addObserver(self, selector: #selector(aSelector), name: aName, object: nil)
        //在此添加任何其他通知
    }
    
    deinit() {
        NotificationCenter.default.removeObserver(self)
    }
    

  • 如果你正在使用combine方法,请使用.sink(receiveValue:)而不是.sink() - Sourabh Shekhar

    0
    我在一个基于文档的应用程序中使用通知。每个文档的观察者类(一个视图控制器)都会捕获通知。打开两个文档,函数被调用两次。打开三个文档...你懂的。 为了限制谁接收通知,您可以指定您对特定对象感兴趣,但这有一个转折:对象需要是类对象的相同实例;您不能简单地比较值;因此UUID不匹配,但是一个实例可以。
    class DocumentID {
        var id = UUID()
    }
    

    运行良好。将此注入您的文档中,以便每个发送或接收通知的类都可以使用,并且您已将通知限制为您的文档。


    0
    在我的情况下,Notification 调用了我在同一屏幕中出现的次数,并触发了相同的操作 X 我进入屏幕的次数。因此,我已经在viewWillDisappear中删除了Notification观察者,这实际上起到了作用,并停止了同一屏幕中多次触发的操作/方法。 感谢Himanth的答案,我找到了解决方法。
    • Swift4

    addObserver

     override func viewDidLoad(){
           super.viewDidLoad()
    
          NotificationCenter.default.addObserver(self, selector: #selector(self.yourNotificationAction(notification:)), name: Notification.Name("yourNotificationName"), object: nil)
    
    }
    
    屏幕消失时,使用removeObserver
     override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            
            NotificationCenter.default.removeObserver(self, name: Notification.Name("yourNotificationName"), object: nil)
          
        }
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,