为什么我的NSNotification观察者被多次调用?

30

我在一个应用程序中使用了多个视图控制器。在其中一个视图控制器上,观察者被初始化如下:

[[NSNotificationCenter defaultCenter] removeObserver:self name:@"MyNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(myMethod:) name:@"MyNotification" object:nil];

即使在初始化NSNotification之前删除它,myMethod:的执行次数仍然会根据相应视图控制器上重复查看的数量进行累加。

为什么会发生这种情况,如何避免myMethod:被调用多次。

注意:我通过使用断点确保没有多次调用postNotification

编辑:这是我的postNotification的样子

NSArray * objects = [NSArray arrayWithObjects:[NSNumber numberWithInt:number],someText, nil];
NSArray * keys = [NSArray arrayWithObjects:@"Number",@"Text", nil];
NSDictionary * userInfo = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
[[NSNotificationCenter defaultCenter] postNotificationName:@"myNotification" object:self userInfo:userInfo];

编辑:即使我把订阅移到viewWillAppear:之后,我仍然得到相同的结果。myMethod:被调用多次。(我重新加载视图控制器的次数)

-(void)viewWillAppear:(BOOL)animated {

    [super viewWillAppear:animated];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"MyNotification" object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(myMethod:) name:@"MyNotification" object:nil];
}

编辑:我的生命周期好像有些问题。ViewDidUnload和dealloc没有被调用,但viewdiddisappear已经被调用。

我将我的ViewController推入堆栈的方式如下,其中父类是一个表视图子类(在单击行时初始化此viewController):

detailScreen * screen = [[detailScreen alloc] initWithContentID:ID andFullContentArray:fullContentIndex andParent:parent];
[self.navigationController pushViewController:screen animated:YES];

解决方案:

将nsnotification的移除操作移到viewdiddisappear中,这个方法很有效。感谢您的指导!


你的解决方案对我有效,谢谢。 - S P Balu Kommuri
@BarryK88 谢谢解决方案:有所帮助。 - Rushi trivedi
我不知怎么搞的,还有一件事会导致类似的问题,就是我声明了 NSString * const ABCSomeNotification 两次... - Bert
5个回答

42

根据这个描述,可能的原因是你的视图控制器被过度保留,并且在你认为它们已经被释放时没有被释放。即使使用 ARC,如果事情被过度保留,这种情况也很普遍。因此,你认为只有一个给定的视图控制器实例处于活动状态,而实际上你有几个活动实例,并且它们都在监听通知。

如果我处于这种情况,我会在视图控制器的dealloc方法中设置断点,并确保它被正确地释放,如果这是你的应用程序预期的设计的话。


我的viewDidUnload和dealloc方法确实没有被调用。但我仍然不知道为什么。(编辑了我的问题) - BarryK88
如果您没有这样做的话,您还应该在视图控制器的dealloc方法中删除self的观察者。否则,NSNotificationCenter将保留视图控制器,并且它永远不会被释放。 - Jaanus
顺便提一下,viewDidUnload仅适用于iOS 6及以下版本。因此,如果您使用的是5以上的任何版本,请不要指望它会触发。 - SmileBot
我不知道引用是否已被删除,但在返回按钮中,我正在执行self.navigationController.popToRootViewController(animated: true)。我的观察者被多次调用。 你能解释一下吗? - Kishor Pahalwani

39

你是用哪些方法注册观察者的?

苹果建议在viewWillAppear:中注册观察者,在viewWillDissapear:中注销观察者。

你确定没有重复注册观察者吗?


我在initWith:启动方法中初始化它。 - BarryK88
@BarryK88 我不会将它添加到启动方法中。原因有两个:1)self 可能没有正确形成,2)它可能永远不会出现在屏幕上,在这种情况下,您可能不想添加通知。 - SmileBot

5
在运行 Swift 的应用程序中遇到了这个问题。第一次启动应用程序时,应用程序会收到通知。通知增加了你进入后台并回来的次数。
  • 应用程序第一次启动 - 在 view will appear 或 view did load 中调用 add observer 一次 - 通知被调用一次
  • 应用程序进入后台并回来,add observer 再次在 view will appear 或 view did load 中被调用。通知被调用两次。
  • 每次进入后台并回来,次数都会增加。
  • 在 view will disappear 中的代码不起作用,因为视图仍然在窗口堆栈中,并未从其中删除。
解决方案: 在您的视图控制器中观察应用程序将 resign active。
  NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationWillResign:", name: UIApplicationWillResignActiveNotification, object: nil)

  func applicationWillResign(notification : NSNotification) {
    NSNotificationCenter.defaultCenter().removeObserver(self)
  }

这将确保您的视图控制器在视图进入后台时会移除通知的观察者。

1
很可能您正在订阅通知。
[[NSNotificationCenter defaultCenter] postNotificationName:@"myNotification" object:self userInfo:userInfo];

在初始化self之前,尝试取消订阅并不真正订阅的'self',这将会收到所有全局myNotification通知。
如果您的视图已经在IB中连接,请使用-awakeFromNib:作为注册通知的起点。

0

观察者模式的类有可能被适当地多次实例化。在调试时,它看起来好像通知被多次发布。但是,如果检查self,你可能会发现每次都是针对不同的实例。

如果您的应用程序使用选项卡栏,并且观察者在您的视图控制器子类的基类中,则很容易出现这种情况。


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