简体   繁体   English

注销KVO时崩溃

[英]Crash while de-registration for KVO

I am seeing some random crashes with my app (although not reproducible when I run through same steps). 我看到我的应用程序出现一些随机崩溃(尽管在执行相同步骤时无法重现)。 I am observing the contentOffset property of the scrollview to take some action when it changes. 我正在观察scrollview的contentOffset属性,以便在更改时采取一些措施。

But I am getting below exception (randomly) with my below code of KVO registration and de-registration. 但是我下面的KVO注册和注销的代码使我(随机)处于例外之下。

Is there any safe check that can be applied here. 有没有可以在这里应用的安全检查。

*** 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"];
}

Your unsubscribing code somehow gets hit more often than the subscribing code. 取消订阅的代码比订阅代码更容易受到打击。 Unfortunately KVO does not handle this nicely and the failure throws an exception rather than just doing nothing as you would expect. 不幸的是,KVO无法很好地处理此问题,并且失败引发异常,而不是像您期望的那样无所事事。 You either need to make sure it only gets hit once, or at least catch the exception like this: 您要么需要确保它仅被击中一次,要么至少要捕获如下异常:

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

As much as I love KVO, this need to balance observing and removing is a real issue. 尽管我很喜欢KVO,但要平衡观察和删除这一需求是一个现实问题。 You will also get crashes if you nil an object that was KVO observing without telling the sending class to de-register you and the class then tries to send an update to the receiver object. 如果您在不通知发送类注销您的情况下将KVO观察的对象设为零,也会使您崩溃,然后该类尝试将更新发送给接收者对象。 Oops! 哎呀! Bad Access! 存取错误!

As there is no official way to ping to see if you have added an observer (which is ridiculous given that a huge part of what goes on in OS is based on KVO), I just use a simple flag system to ensure that nothing gets added more than once. 由于没有官方的ping方法来查看是否添加了观察者(鉴于操作系统中很大一部分内容是基于KVO的,所以这很可笑),因此我仅使用简单的标记系统来确保未添加任何内容不止一次。 In the example below, I have a bool flag to track the observer status. 在下面的示例中,我有一个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];
  }

Yes, using flags to check state is frowned upon by the coding-police. 是的,编码警察不赞成使用标志来检查状态。 But there really is no other way to be sure except to bean count. 但是除了豆数之外,实际上没有其他方法可以确定。

Whatever you do, remember that some classes need to observe even after viewWillDisappear is called (but the view still exists somewhere in the hierarchy), so you can't just do the observe/remove from viewWillAppear/WillDisappear trick. 无论您做什么,请记住,即使在调用viewWillDisappear之后某些类也需要进行观察(但是视图仍然存在于层次结构中的某个位置),因此您不能只是从viewWillAppear / WillDisappear技巧中进行观察/删除。 You may need to use delegate callbacks to the object that created the view (and will destroy it) to ensure you never leave a KVO calling class hanging. 您可能需要对创建视图的对象使用委托回调(并将破坏视图),以确保您永远不会挂起KVO调用类。 Having said that, I'm no expert in this stuff, so I'm sure there are people who can do this with way more finesse than my patented bean-counting lol 话虽这么说,我不是这方面的专家,所以我敢肯定有些人可以比我获得专利的豆子计数大声笑更精巧

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM