当控制器被释放时,键值观察者仍然与其注册。

5
我在代码中添加了一个观察者,然后在dealloc和viewWillDisappear中将其移除,但仍然出现错误提示。

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x167e5980 of class MyController2 was deallocated while key value observers were still registered with it.

Current observation info: <NSKeyValueObservationInfo 0x16719f90> (
<NSKeyValueObservance 0x16719fb0: Observer: 0x167e5980, Key path: dataContainer.report, Options: <New: YES, Old: YES, Prior: NO> Context: 0x0, Property: 0x1677df30>
)'

我创建了一个控制器MyController,并从它派生了一个新的控制器MyController2。现在我在MyController2中添加了KVO。

- (void)viewDidLoad {
    [super viewDidLoad];
    [self addObserver:self forKeyPath:@"dataContainer.report" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}

然后在observeValueForKeyPath方法中:

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {

    id oldC = [change objectForKey:NSKeyValueChangeOldKey];
    id newC = [change objectForKey:NSKeyValueChangeNewKey];

    if([keyPath isEqualToString:@"dataContainer.report"]) {
        if (oldC != newC) {
            //Remove Observer

            [self removeObserver:self forKeyPath:@"dataContainer.report" context:nil];
            [self updateDataContainer];
            [self reportView];
        }
    }
}

然后我尝试在viewWillDisappear和dealloc中删除观察器:

- (void)dealloc {
    @try{
        [self removeObserver:self forKeyPath:@"dataContainer.report" context:nil];
    }@catch(id anException){
    }
}

-(void) viewWillDisappear:(BOOL)animated{
    @try{
        [self removeObserver:self forKeyPath:@"dataContainer.report" context:nil];
    }@catch(id anException){
    }
    [super viewWillDisappear:animated];
}

我看了很多帖子,它们都说同一件事情:你需要移除观察者。我试图从两个地方移除观察者,但仍然遇到了问题。


错误显示您已为 dataContainer.inspectionReport 键路径添加了观察者,但您的代码将键路径显示为 dataContainer.report - Midhun MP
@MidhunMP 抱歉,实际上我已经从代码中更改了那部分并忘记从错误中更改。现在我已经更改了它。如果您发现任何原因导致我出现错误,请告诉我。 - Sudhanshu Gupta
我建议在添加和删除观察者的每个地方都记录日志,我敢打赌你会发现它们不平衡。 - Rob
@Rob 我会尝试去做。而且我认为我不明白为什么我被踩了。至少那些踩我帖子的人也应该写下原因。这样我才不会再犯同样的错误。 - Sudhanshu Gupta
你是从其他地方添加观察者吗? - Midhun MP
显示剩余4条评论
3个回答

11

根据我的经验,iOS 中添加和移除观察者的最佳方法是:

在 ViewDidLoad 中添加观察者:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self addObserver:self forKeyPath:@"dataContainer.report" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}

为了观察观察者,我们需要这样做:-

不要在observeValueForKeyPath中移除观察者

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {

    id oldC = [change objectForKey:NSKeyValueChangeOldKey];
    id newC = [change objectForKey:NSKeyValueChangeNewKey];

    if([keyPath isEqualToString:@"dataContainer.report"]) {
        if (oldC != newC) {
            [self updateDataContainer];
            [self reportView];
        }
    }
}

在dealloc中移除观察者:

在这里只需调用一次remove方法

- (void)dealloc {
    @try{
        [self removeObserver:self forKeyPath:@"dataContainer.report" context:nil];
    }@catch(id anException){
    }
}

3

当添加观察者时,您应该有一个布尔标志,并将其设置为true,在删除时将其设置为false。只有在此标志为false时才添加观察者。 在viewWillDisappear中删除观察者之前,还要添加检查。同时添加日志。

    if (self.isMovingFromParentViewController || self.isBeingDismissed) {
        if (isReportKVOAdded) {
            [self removeObserver:self forKeyPath:@"dataContainer.report" context:nil];
        }
    }

你需要删除观察者的次数与添加的次数相同,这可以通过使用标志来防止。 - Atif
是的,为添加和移除观察者使用一个布尔值是个不错的主意。这对我很有效。 - Chandramani

1

使用KeyPath观察者的Swift 4 KVO适用于iOS 11(可能也适用于macOS 10.13)

    @objcMembers class Foo: NSObject {
        dynamic var string = "bar"
    }

    var observation: NSKeyValueObservation!

    func TestKVO() {

        var foo = Foo()

        // kvo in 2 lines of code
        observation = foo.observe(\.string) { observed, change in
            print(observed.string)
        }

        foo.string = "yo" // prints "yo"
        foo = Foo() // works on iOS 11, crashes MacOS 10.12, not tested on MacOS 10.13, yet
        foo.string = "oy" // does not print
    }

macOS 10.13 和 iOS 11 发布说明


1
需要注意的是,我正在使用这种技术,但在macOS 10.13.2 / XCode 9.2上遇到了一些问题。我的观察闭包中出现了一些“尝试访问未拥有的self,但它已经被释放”的崩溃。在我的类中添加deinit { observation?.invalidate() }可以防止崩溃-因此某些内容没有被正确清理。 - Giles

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