在取消注册KVO时崩溃

时间:2014-08-11 22:50:56

标签: ios objective-c cocoa-touch key-value-observing

我看到我的应用程序发生了一些随机崩溃(虽然在我执行相同步骤时无法重现)。我正在观察scrollview的contentOffset属性,以便在更改时采取一些操作。

但是我在下面的KVO注册和注销代码中获得了以下异常(随机)。

是否可以在此处应用任何安全检查。

*** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <MyPagingController 0x1f05e460> for the key path "contentOffset" from <UIScrollView 0x1f0a8fd0> because it is not registered as an observer.'

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self.scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}

- (void)viewWillDisappear:(BOOL)iAnimated {
    [super viewWillDisappear:iAnimated];
    [self.scrollView removeObserver:self forKeyPath:@"contentOffset"];
}

2 个答案:

答案 0 :(得分:1)

您的取消订阅代码会比订阅代码更频繁地受到攻击。不幸的是,KVO并没有很好地处理这个问题而且失败会引发异常而不是像你期望的那样无所事事。您要么确保它只被击中一次,要么至少捕获这样的异常:

@try 
{
  [self.scrollView removeObserver:self forKeyPath:@"contentOffset"];
} 
@catch (NSException * __unused exception) {}
}

答案 1 :(得分:0)

尽管我喜欢KVO,但这需要平衡观察和移除是一个真正的问题。如果您没有告诉发送类取消注册您,并且该类尝试向接收方对象发送更新,那么如果您没有看到KVO正在观察的对象,您也会崩溃。哎呀!访问不好!

由于没有正式的ping方式来判断你是否添加了观察者(考虑到操作系统中发生的很大一部分是基于KVO,这是荒谬的),我只是使用一个简单的标记系统来确保什么都不会被添加多次。在下面的示例中,我有一个bool标志来跟踪观察者状态。

@interface RepTableViewController ()
@property (nonatomic, assign) BOOL KVOSet;
@end

#pragma mark - KVOObserving

- (void)_addKVOObserving
   {
      //1. If the BOOL flag shows we are already observing, do nothing.
      if (self.KVOSet) return;

      //2. Set the flag to YES BEFORE setting the observer as there's no guarantee it will happen immediately.
      self.KVOSet = YES;

      //3. Tell your class to add you up for the object / property you want to observe.
      [[ELRepresentativeManager sharedManager]addObserver:self
       forKeyPath:@"myRepresentative"
       options:NSKeyValueObservingOptionNew
       context:nil];
   }

- (void)_removeKVOObserving
   {
       //1. Do nothing if we have not set an observer
       if (!self.KVOSet) return;

       //2. If we have an observer, set the BOOL flag to NO before removing
       self.KVOSet = NO;

       //3. Remove the observer
       [[ELRepresentativeManager sharedManager] removeObserver:self  
       forKeyPath:@"myRepresentative" context:nil];
  }

是的,编码警察不赞成用旗帜检查状态。但除了豆子计数之外,没有别的办法可以确定。

无论你做什么,请记住,即使在调用viewWillDisappear之后,某些类仍需要观察(但视图仍然存在于层次结构中的某个位置),因此您无法从viewWillAppear / WillDisappear技巧中执行观察/移除操作。您可能需要使用委托回调来创建视图的对象(并将其销毁),以确保您永远不会挂起KVO调用类。话虽如此,我并不是这方面的专家,所以我确信有些人能够以比我专利的豆类计数更精细的方式做到这一点