Swift - 使用两个不同的 OperationQueue 和 KVO 时应用程序崩溃

Posted

技术标签:

【中文标题】Swift - 使用两个不同的 OperationQueue 和 KVO 时应用程序崩溃【英文标题】:Swift - Application crash when using two different OperationQueues with KVO 【发布时间】:2018-05-26 19:07:00 【问题描述】:

我使用 JSON 获取两种类型的信息,并使用 addObserver(forKeyPath:"operations"...) 将“操作”添加到 2 个不同的操作队列类。 在函数 observeValue 中,我正在检查 operationQueue1.operations.isEmpty 是否,然后在 UI 中刷新我的信息。我对 operationQueue2 的 if else 做同样的事情,但是当两个操作在某个时候启动时,应用程序崩溃并显示错误消息:*** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <AppName.ViewController 0x102977800> for the key path "operations" from <AppName.OperationQueue1 0x1c4a233c0> because it is not registered as an observer。 仅启动 1 次操作时我没有问题。有什么建议吗?

func getInfo1()//runned in viewDidLoad
  operationQueue1.addObserver(forKeyPath:"operations"...)
  operationQueue1.dataTask(URL:"..."....)
      DispatchQueue.main.async
  NotificationCenter.default.postNotification(NSNotification.Name(rawValue: "NewDataReceived1", userInfo:infoFromTheWebsite)
      
  


func NewDataReceived1()
  here I add the information to arrays to be loaded in tableView1


HERE IS THE CODE FOR 2ND INFO WHICH IS THE SAME

override func observeValue(forKeyPath keyPath: String?, ....)
        if(object as? operationQueue1 == operationQueue1Class && keyPath == "operations" && context == context1)
             if(operationQueue1.operations.isEmpty)
                  DispatchQueue.main.async
                        operationQueue1..removeObserver(self, forKeyPath:"operations")
                        Timer.scheduled("refreshingTableInformation1") 
                   
             
        else if(operationQueue2....)
             SAME AS OPERATION 1, BUT USING DIFFERENT FUNC TO REFRESH TABLE INFORMATION AND THE TABLES ARE DIFFERENT
        else
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        


func refreshingTableInformation1()
     tableView1.reloadData()
     Timer.scheduled("getInfo1", repeat:false)


func refreshingTableInformation2()
     tableView2.reloadData()
     Timer.scheduled("getInfo2", repeat:false)

有时工作 10 秒后崩溃,有时工作超过 60 秒然后崩溃...

【问题讨论】:

就像错误所说的那样,即使视图控制器未注册为观察者,您也试图从观察 OperationQueue1 的操作键路径中删除视图控制器。如果您更改了对 vc 的引用,则可能会发生这种情况。发布您的操作队列的代码以及添加观察者的位置。 我在 2 个请求之前添加了观察者,它们被锁定在无限循环中。当我更新 UI 中的信息时,我再次设置了观察者,并开始请求再次获取信息。 您的代码不是很清楚。无论如何,为什么不在收集“NewDataReceived1”的数组上放置一个观察者呢?会更容易处理。此外,如果您有同步问题,最好使用诸如 Alamofire 之类的库来处理您的 http 请求。这样可以省去很多麻烦。 我不会在 NewDataReceived1 中添加观察者,因为我正在使用 operationQueue1.dataTask 3 次,这意味着我要设置 3 个观察者... 【参考方案1】:

您的代码容易受到竞争条件的影响。考虑以下场景:

    调用getInfo1(),向operationQueue1添加一个操作。

    操作完成,这意味着您的 KVO 观察被调用。队列现在是空的,因此您的观察计划在主调度队列中移除您的观察者。

    现在,在您提交到主队列的操作能够运行之前,调用 getInfo1(),它会向 operationQueue1 添加一个新操作,该操作在您在步骤中排队的操作之前完成2 有机会运行(嘿,也许主队列正忙于某些事情;这很容易发生,因为它是一个串行队列)。

    您观察到第一次调用getInfo1() 在队列为空时被调用再次,导致另一个 取消注册块提交到主队列。

    这两个注销块最终在主队列上执行。第二个使程序崩溃,因为您已经注销了观察者。

您可以改用 Swift 4 的基于块的观察者并将观察者设置为 nil 而不是显式注销它来解决这个问题(假设代码没有更多这种性质的问题)。但是,我建议 KVO 是 wrong tool 用于您正在尝试做的事情。就像老《水晶探秘》游戏的说明书上说的,有点像用高射炮杀蚊子。

从我从上面的代码中可以看出,您使用 KVO 似乎只是为了安排您提交到队列的操作或一组操作何时完成的通知。根据您的 dataTask 方法实际执行的操作,我将执行以下操作:

如果您只提交一个操作:将操作的 completionBlock 属性设置为刷新表信息的闭包。

如果您提交多个操作:创建一个新的 BlockOperation 以刷新您的表信息,并在该操作上调用 addDependency 以及您提交到队列的所有其他操作。然后,提交该操作。

这将为您提供一种更清洁、更轻松的方式来监控您的任务完成情况。而且由于您不再需要完全清空队列,您甚至可能不再需要使用两个单独的队列,具体取决于您对它们进行的其他操作。

【讨论】:

我正在使用 KVO,因为我有动态表格视图,如果网站没有返回我的信息,我没有创建表格视图 @BogdanBogdanov 您能否更具体地说明您在做什么?我不明白为什么推迟表视图的创建需要在操作队列上使用 KVO,而不是仅仅在完成操作中进行。 我同意@CharlesSrstka 的观点,我的第一个想法是比赛条件,尤其是他上面列表中的第 4 位。使用较新的tableView.beginUpdates().endUpdates()performBatchUpdates(_:completion:) 可以从block-observer 的changeHandler 更新tableView。请注意,observe(_:options:changeHandler:) 会在需要时调用 addObserver_:forKeyPath:options:context:)removeObserver(_:forKeyPath:context:)

以上是关于Swift - 使用两个不同的 OperationQueue 和 KVO 时应用程序崩溃的主要内容,如果未能解决你的问题,请参考以下文章

583. Delete Operation for Two Strings

理解Swift中具有依赖性的操作序列

Swift - 使用两个不同的 OperationQueue 和 KVO 时应用程序崩溃

如何使用 Swift 将具有两个不同键的两个值存储到数组或字典中? [关闭]

Swift:prepareForSegue 有两个不同的 segue

将两个不同的数组加载到 tableview (Swift)