代码之家  ›  专栏  ›  技术社区  ›  Abhinav

KVO注销时崩溃

  •  0
  • Abhinav  · 技术社区  · 10 年前

    我看到我的应用程序出现了一些随机崩溃(尽管当我执行相同的步骤时,不可再现)。我正在观察滚动视图的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 回复  |  直到 10 年前
        1
  •  1
  •   Dima    10 年前

    你的取消订阅代码比订阅代码更容易受到攻击。不幸的是,KVO不能很好地处理这一问题,失败会引发异常,而不是像您所期望的那样什么都不做。您要么需要确保它只命中一次,要么至少捕获如下异常:

    @try 
    {
      [self.scrollView removeObserver:self forKeyPath:@"contentOffset"];
    } 
    @catch (NSException * __unused exception) {}
    }
    
        2
  •  0
  •   Mike Critchley    7 年前

    尽管我非常喜欢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调用类挂起。话虽如此,但我不是这方面的专家,所以我相信有人比我的专利数豆lol更能做到这一点