【问题标题】:When to unregister KVO observation of Operation isFinished何时注销 Operation isFinished 的 KVO 观察
【发布时间】:2017-04-06 19:38:22
【问题描述】:

在这个简单的代码(Xcode 8.3)中,我创建了一个 Operation 子类实例,注册其isFinished 属性的 KVO 观察,并通过将其添加到我的队列来启动该操作:

class MyOperation : Operation {
    override func main() {
        print("starting")
        print("finishing")
    }
}

class ViewController: UIViewController {
    let q = OperationQueue()
    override func viewDidLoad() {
        super.viewDidLoad()
        let op = MyOperation()
        op.addObserver(self, forKeyPath: #keyPath(MyOperation.isFinished), options: [], context: nil)
        self.q.addOperation(op)
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        print("Observed \(keyPath)")
        if let op = object as? Operation {
            op.removeObserver(self, forKeyPath: #keyPath(MyOperation.isFinished))
        }
    }
}

如您所见,当然,我有一个observeValue(forKeyPath... 的实现,我的计划是在那里调用removeObserver(forKeyPath...

问题是我的应用程序崩溃并显示“MyOperation 已解除分配,而键值观察者仍向其注册”。我们打印“开始”和“结束”,但我们从不打印“已观察”;该操作不存在我收到我的 KVO 通知之前。

这似乎是第 22 条规则。如果我不能通过观察isFinished 来移除观察者,我应该什么时候做呢? [我可以通过将我在main 末尾设置的我自己的 KVO 可观察属性添加到 MyOperation 来解决这个问题。但是我必须这样做的想法很奇怪。这不正是为什么isFinished 是可观察的,所以我可以在这里做我想做的事吗?]

【问题讨论】:

  • 会不会是developer.apple.com/library/content/technotes/tn2109/…提到的问题? – “当您使用键值观察 (KVO) 来观察 NSOperation 的 isFinished 属性时,可能会出现类似的问题。虽然 KVO 不保留观察者或被观察者,但仍有可能,即使您在您的-viewWillDisappear: 方法,KVO 通知可能已经为您的对象发送。如果发生这种情况,运行通知的线程最终可能会调用一个已释放的对象!"
  • @MartinR 注释的那部分讨论了self(观察者)可能不存在的危险,因此可能会将KVO 通知发送到不存在的对象。这与我的问题相反;我的问题是操作(观察者)在没有向我发送我的 KVO 通知的情况下将不复存在。 self 是根视图控制器,无处可去。
  • 有什么用处可以提一下,我试过了,我可以看到:starting finishing Observed Optional("isFinished")?我的应用不会崩溃。我用的是xcode 8.2,希望能明白是怎么回事……
  • @AhmadF 如果您在 Xcode 8.2 中看到该结果并且没有崩溃,则表明这可能是(令人难以置信的)Xcode 8.3 中的一个新错误。我将在我的另一台机器(仍然有 Xcode 8.2)上尝试它,看看我是否可以确认。我会让你知道我发现了什么。谢谢!
  • @AhmadF 你说得对。我邀请您输入您在 Xcode 8.2 中尝试过并且所有三个 print 语句都打印并且我们不会崩溃的事实作为答案,并暗示这可能是 Xcode 中的错误(或至少是新行为) 8.3.我会接受这个答案。非常感谢! (我还将向 Apple 提交错误报告。)

标签: swift key-value-observing nsoperation


【解决方案1】:

Xcode 8.2 上测试完全相同的给定代码 sn-p 后,它可以正常工作,控制台显示:

starting
finishing
Observed Optional("isFinished")

似乎问题的原因是在 Xcode 8.3 上测试它,可能是一个错误 - 或者它可能是一个新行为 -。但是,我建议将其报告为错误。

【讨论】:

  • 好吧,事实证明这个 bug 不是 Cocoa Foundation 的 bug——它是一个涉及 #keyPath 的 Swift 语言 bug。解决方法是改用文字字符串编写op.addObserver(self, forKeyPath: "isFinished", options: [], context: nil)
  • 这是 Swift 错误报告:bugs.swift.org/browse/SR-4397 我也提交了我的,但它被(正确地)标记为重复。
  • @matt 但原因是什么:我们应该将其实现为forKeyPath: "isFinished" 而不是#keyPath(MyOperation.isFinished)?在我看来,这个案例类似于使用#selector
  • 但这正是错误所在。 Swift 3.0 中的 forKeyPath: #keyPath(MyOperation.isFinished) 等同于 forKeyPath: "isFinished",所以它可以工作。但在 Swift 3.1 中却没有。
【解决方案2】:

Apple 在 Swift 3.1 (source) 中更改了 #keyPath 的行为。目前#keyPath(isFinished) 返回"finished",它曾经返回"isFinished"这是一个错误。这是解释,因为在使用 KVOOperation 类时很容易混淆。


当您为 KVO 通知注册对象时

textView.addObserver(self,
                     forKeyPath: #keyPath(UITextView.isEditable),
                     options: [.new, .old],
                     context: nil)

Foundation 为其 (textView) 提供了调用 willChangeValue(forKey:)didChangeValue(forKey:) (this is done via isa-swizzling) 的新 setter 实现。传递给这些方法的键不是 getter (isEditable) 不是 setter(setEditable:) 而是属性名称 (editable)。

@property(nonatomic,getter=isEditable) BOOL editable

这就是为什么它会收到来自#keyPath(UITextView.isEditable)editable


虽然 Operation 类具有定义的属性和跟随

@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;

它希望观察isFinishedisExecuting 键的通知。

在完成或取消其任务后,您的并发操作对象必须为 isExecuting 和 isFinished 键路径生成 KVO 通知,以标记您的操作的最终状态更改

这迫使我们在发布这些通知时使用文字字符串。

恕我直言,这是多年前犯的一个错误,如果不破坏现有代码,真的很难从中恢复。

【讨论】:

  • 是的,我都知道。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-12
  • 1970-01-01
  • 2017-11-15
  • 1970-01-01
  • 2013-12-04
  • 2012-01-16
  • 1970-01-01
相关资源
最近更新 更多