2017-04-06 41 views
1

在这个简单的代码(的Xcode 8.3),创建一个操作子类的实例,注册其isFinished财产的志愿观测,并通过将其添加到我的队列启动操作:当注销操作的志愿观测isFinished

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通知。

这看起来像一个catch-22。如果我不能通过观察isFinished删除观察者,那么我应该怎么做? [我可以通过向MyOperation添加我自己的KVO可观察属性来解决此问题,该属性在main末尾处设置。但我应该这样做的想法很奇怪,是不是这正是为什么isFinished是可观的,所以我可以做什么,我想在这里?]

+1

难道这是https://developer.apple.com/library/content/technotes/tn2109/_index.html中提到的问题吗? - “使用键值观察(KVO)观察NSOperation的isFinished属性时可能会出现类似的问题,虽然KVO不保留观察者或观察者,但即使您删除了观察者-viewWillDisappear:方法,KVO通知可能已经在您的对象的运行中,如果发生这种情况,运行通知的线程最终可能会调用一个释放对象! –

+0

@MartinR本节注释的是“自我”(观察者)可能不存在的危险,因此KVO通知可能会发送给不存在的对象。这是我的问题的反映;我的问题是,操作(观察者)将不存在而不向我发送我的KVO通知。 'self'是根视图控制器,并且无处可去。 – matt

+0

是否有任何用处提及我尝试过,我可以看到:'开始完成Observed Optional(“isFinished”)'?我的应用程序不会*崩溃。我正在使用xcode 8.2,我希望我明白情况如何...... –

回答

2

测试完全相同的给定的代码片段的Xcode 8.2后,它的工作,因为它应该,控制台显示:

starting 
finishing 
Observed Optional("isFinished") 

看来,这个问题的原因是在的Xcode 8.3测试它,可能它是一个错误 - 或者它可能是一个新的动作 - 。不过,我建议将它报告为一个错误。

+1

好吧,事实证明,这个错误不是可可基金会的错误 - 它是一个涉及'#keyPath'的Swift语言错误。解决方法是使用文字字符串来代替编写op.addObserver(self,forKeyPath:“isFinished”,options:[],context:nil)'。 – matt

+0

这里是Swift错误报告:https://bugs.swift.org/browse/SR-4397我也提交了我的,但它被正确地标记为重复。 – matt

+0

@matt但是什么原因:我们应该将它作为'forKeyPath:“isFinished”'而不是'#keyPath(MyOperation.isFinished)'来实现呢?这种情况在我看来与使用'#selector'类似。 –

2

Apple在Swift 3.1中更改了#keyPath行为(source)。目前#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)新二传的实现。传递给这些方法的密钥不是获取者(isEditable)而不是设置者(setEditable:),而是属性名称(editable)。

@property(nonatomic,getter=isEditable) BOOL editable 

这就是为什么它预计从#keyPath(UITextView.isEditable)接收editable


虽然操作类具有定义广告是否符合

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

它希望遵守通知isFinishedisExecuting键。

完成后或者其任务的取消,您的并发操作对象必须生成两个isExecuting志愿通知和isFinished关键路径标记状态的最终变化为您的操作

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

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

+0

是的,我知道这一切。 – matt