在这个简单的代码(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被解除分配,而键值观察者仍然注册了#34;。我们打印"开始"和"整理"但我们从不打印"观察&#34 ;;在我获得KVO通知之前,操作不存在。
这看起来像是一个捕获22。如果我不能通过观察isFinished
来移除观察者,我应该什么时候这样做? [我可以通过向MyOperation添加我在main
末尾设置的自己的KVO可观察属性来解决此问题。但我必须这样做的想法很奇怪;这不是isFinished
可观察的原因,所以我可以做我想在这里做的事情吗?]
答案 0 :(得分:2)
在 Xcode 8.2 上测试完全相同的给定代码片段后,它可以正常工作,控制台显示:
starting
finishing
Observed Optional("isFinished")
问题的原因似乎是在 Xcode 8.3 上测试它,可能是一个错误 - 或者它可能是一个新行为 - 。但是,我建议将其报告为错误。
答案 1 :(得分:2)
Apple改变了Swift 3.1中的#keyPath
行为(source)。目前#keyPath(isFinished)
会返回"finished"
,它会返回"isFinished"
,会返回错误。以下是解释,因为使用KVO
和Operation
类时很容易引起混淆。
为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
这就是为什么预计会从editable
收到#keyPath(UITextView.isEditable)
。
虽然Operation类具有定义的属性,但是
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
它希望观察isFinished
和isExecuting
键的通知。
完成或取消其任务后,您的并发操作对象必须为isExecuting和isFinished键路径生成KVO通知,以标记操作的最终状态更改
这会强制我们在发布这些通知时使用文字字符串。
恕我直言,这是多年前的错误,如果不破坏现有代码,很难恢复。