【问题标题】:Correct management of addObserverForName:object:queue:usingBlock:正确管理 addObserverForName:object:queue:usingBlock:
【发布时间】:2011-05-31 20:24:38
【问题描述】:

我对objective-c 中的块还很陌生,想知道我的这个伪代码是否正确。我不确定仅删除观察者是否足够,或者我是否必须调用 removeObserver:name:object:

-(void) scan {
    Scanner *scanner = [[Scanner alloc] init];
    id scanComplete = [[NSNotificationCenter defaultCenter] addObserverForName:@"ScanComplete" 
                        object:scanner 
                        queue:nil 
                        usingBlock:^(NSNotification *notification){
                            /*
                             do something
                             */
                            [[NSNotificationCenter defaultCenter] removeObserver:scanComplete];
                            [scanner release];
                        }];
    [scanner startScan];
}

更新:我从这个区块收到间歇性的EXC_BAD_ACCESS,所以这不可能。

【问题讨论】:

    标签: ios objective-c cocoa objective-c-blocks


    【解决方案1】:

    在定义块本身之前声明scanComplete 变量。

    您需要这样做的原因是因为您正在尝试访问一个在定义时不存在于块中的变量,因为该变量本身尚未被分配。

    EXC_BAD_ACCESS 是什么?好吧,这是当您尝试访问不存在的引用时引发的异常。所以这正是你的例子中的情况。

    因此,如果您在块本身之前声明变量,那么它应该可以工作:

    -(void) scan {
        Scanner *scanner = [[Scanner alloc] init];
        __block id scanComplete;
        scanComplete = [[NSNotificationCenter defaultCenter] addObserverForName:@"ScanComplete" 
                            object:scanner 
                            queue:nil 
                            usingBlock:^(NSNotification *notification){
                               /*
                               do something
                               */
                               [[NSNotificationCenter defaultCenter] removeObserver:scanComplete];
                               [scanner release];
                        }];
        [scanner startScan];
    }
    

    【讨论】:

    • 你需要__block id scanComplete;,否则它会被复制到块中,你会泄露观察者。
    • 在 ARC 的世界中,关于使用__block 避免捕获的评论不再适用。 确实成立的是,__block 限定符解决了一个基本问题:定义块时,addObserverForName:... 尚未返回,因此捕获的值最多为 nil (在 ARC 下运行时,因为它在变量声明中隐式自动为零),或者 undefined,用一个 BAD_ACCESS 换取完全未定义的行为,最坏的情况......
    • 通过引用块内部的局部变量来删除观察者总是很恶心。将返回的观察者令牌(此处为scanComplete)存储为实例变量;在 ARC 下,这应该是一个 __weak 实例变量,以防止自身的保留循环。
    • @matt 为什么?想要完全本地化与基于块的通知相关的所有移动部件的范围有什么不妥?
    • [scanner release]; 应该在块外
    【解决方案2】:

    块的范围没有释放扫描器对象的权限。如果您不使用垃圾收集,删除 release 并让扫描仪自动释放 ([[[Scanner alloc] init] autorelease]) 应该可以解决问题。

    您还应该能够安全地将呼叫转移到块外的removeObserver

    对于EXC_BAD_ACCESS的情况:在应用程序崩溃后在控制台窗口中输入bt会给你一个回溯,并且应该告诉你错误发生在哪里。

    【讨论】:

      【解决方案3】:

      您不应在注册块中取消注册。相反,将从addObserverForName(在本例中为您的scanComplete)返回的令牌存储为实例变量或作为实例变量的集合中,然后在您即将不存在时取消注册(例如dealloc)。我所做的是保留一个名为observers 的 NSMutableSet。所以:

      id ob = [[NSNotificationCenter defaultCenter] 
           addObserverForName:@"whatever" object:nil queue:nil 
           usingBlock:^(NSNotification *note) {
              // ... whatever ...
      }];
      [self->observers addObject:ob];
      

      然后:

      for (id ob in self->observers)
          [[NSNotificationCenter defaultCenter] removeObserver:ob];
      self->observers = nil;
      

      【讨论】:

      • 如果您想要一次性通知,我不明白您为什么不能在块本身中取消注册。
      • 同意上述评论。我认为这个答案缺少的是“为什么”。我认为解释为什么“您不应该在注册块中取消注册”会有所帮助。特别是因为苹果自己的这个 API 的文档包括建议在该块中取消注册......
      • @DanielGalasko 问题是如何防止崩溃,我的回答正确地说明了一种方法。接受的答案从另一端解决问题,ob 的声明。这两个答案都是防止崩溃的正确方法。
      【解决方案4】:

      有关此方法的 Apple 文档:

      以下示例展示了如何注册以接收区域设置更改通知。

      NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
      NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
      self.localeChangeObserver = [center addObserverForName:NSCurrentLocaleDidChangeNotification object:nil
          queue:mainQueue usingBlock:^(NSNotification *note) {
      
              NSLog(@"The user's locale changed to: %@", [[NSLocale currentLocale] localeIdentifier]);
          }];
      

      要取消注册观察,请将此方法返回的对象传递给 removeObserver:。您必须在 addObserverForName:object:queue:usingBlock: 指定的任何对象被释放之前调用 removeObserver: 或 removeObserver:name:object:。

      NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
      [center removeObserver:self.localeChangeObserver];
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-07-25
        • 2021-06-18
        • 2014-05-30
        • 2015-07-12
        • 1970-01-01
        • 1970-01-01
        • 2013-11-26
        • 2012-04-13
        相关资源
        最近更新 更多