Swift - application crash when using two different operations with KVO

I get two types of information with JSON, and I add "operations" to two different classes of operation queues with addObserver (forKeyPath: "operations" ...). In the observValue function, I check if operationQueue1.operations.isEmpty, and then update my information in the user interface. I do the same with if else with operationQueue2, but when 2 operations start sometime, the application crashes with an error message: *** 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 . 'I have no problem running only 1 operation. Any suggestions?

 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) } 

Sometimes it works 10 seconds and crashes, and sometimes it works more than 60 seconds, and then it crashes ...

+7
queue swift crash key-value-observing
source share
1 answer

Your code here is vulnerable to race conditions. Consider the following scenario:

  • getInfo1() , which adds an operation to operationQueue1 .

  • The operation is in progress, which means that you are invoking your observation of the CVO. The queue is now empty, so your observations plan to remove your observer in the main dispatch queue.

  • Now, before the operation that you sent to the main queue can start, something else calls getInfo1() , which adds a new operation to operationQueue1 , which completes before the operation you queue in step 2 had a chance to start (hey maybe the main line was busy with something, it’s easy for this to happen, because this is the next line).

  • Your observation of the first call to getInfo1() is called again while the queue is empty, as a result of which another registration block is sent to the main queue.

  • Two dividing block registers will finally be executed in the main queue. The second is a program crash, because you have already unregistered your observer.

Perhaps you can fix this problem (assuming the code no longer has this kind of problem) by using observers based on Swift 4 blocks instead and setting the observer to nil instead of explicitly excluding it from being registered. However, I suggest that KVO be the wrong tool for what you are trying to do. As the instructions for the old Crystal Quest game used to say, this is a bit like using anti-aircraft weapons to destroy a mosquito.

From what I see from the above code, it looks like you are using KVO only to schedule notification of when the operation or group of operations that you are sending to the queue completes. Depending on what your dataTask method actually does, here is what I would do instead:

  • If you send only one operation: set the completionBlock property to a close operation that updates the information about your table.

  • If you submit multiple operations: Create a new BlockOperation that updates the information about your table and calls addDependency in this operation with every other operation that you send to the queue. Then submit this operation.

This will provide you with a cleaner and more reliable way to monitor the completion of your tasks. And since you no longer need a queue to completely empty, you no longer need to use two separate queues, depending on what you do with them.

+4
source share

All Articles