How to properly add operations to the main queue

So I need an Alert to present itself before some UI updates happen. Thus, this is what I did:


let alert = UIAlertController(title: "Loading!" , message: "Your data is loading", preferredStyle: .alert)
            let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
            alert.addAction(ok)
            ok.isEnabled = false
            
            let queue = OperationQueue()
            queue.qualityOfService = .userInteractive
            
            queue.addOperation {
                if (UserDefaults.standard.integer(forKey: "numAllowed") == 3){     //it only presents depending on a setting
                    OperationQueue.main.addOperation {
                        self.present(alert, animated: true, completion: {print ("it presented")})
                    }
                }
                
                OperationQueue.main.addOperation{
                    
                    var newValues = URLResourceValues()
                    newValues.isHidden = true
                    try! cell.videoURL?.setResourceValues(newValues)
                    try! cell.urlJpg?.setResourceValues(newValues)
                    self.refresh()
                    self.tableView.reloadSections([0], with: .fade)
                    self.refresh()
                    self.tableView.reloadSections([0], with: .fade)
                    ok.isEnabled = true
                }
            }



This has worked all but two times. The times it doesn't work what happens is it'll run lines 16-26, than present Alert at line 12. It doesn't make sense to me why!

Inside the "addOperation" closure starting at line 09, you are actually giving the main thread three things to do:


1. Run line 12 as an Operation.


2. Present an alert controller.


3. Run lines 18-26 as an Operation.


Because of line 09, you're queuing these three things on a background thread. That means #1 will probably start running on the main thread immediately. That in turn means there are now two thread executing at the same time (ignore other threads and other factors). The background thread is executing line 16 to queue up #3, and the main thread is executing line 12 to queue up #2.


It's essentially random whether #2 or #3 gets queued to the main thread first. On a given device with a given level of other activity, you will usually see them in a particular order, but there's nothing to guarantee that. Your observed behavior (#2 usually gets queued first, but occasionally #3 gets there first) sounds like exactly what can be expected. Once #2 and #3 are queued, they will of course execute in the order they were queued.


If you want to ensure that #3 follows #2, you should reorganize the code to use only one OperationQueue.main.addOperation invocation. (Or use two, but let your "if" statement choose only one to execute. That would involve some source code duplication.) You might consider factoring lines 18-26 out into a separate method to help this reorganization.


Side-issue: It's not clear why you're using the background OperationQueue here. It doesn't seem to achieve anything here. What is its purpose?

Thanks Quincy, as always! So what I should have done is something like this:


let alert = UIAlertController(title: "Loading!" , message: "Your data is loading", preferredStyle: .alert)
         let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
         alert.addAction(ok)
         ok.isEnabled = false
            
         if (UserDefaults.standard.integer(forKey: "numAllowed") == 3){     //it only presents depending on a set
             OperationQueue.main.addOperation {
             self.present(alert, animated: true, completion: {print ("it presented")})
             var newValues = URLResourceValues()
             newValues.isHidden = true
             try! cell.videoURL?.setResourceValues(newValues)
             try! cell.urlJpg?.setResourceValues(newValues)
             self.refresh()
             self.tableView.reloadSections([0], with: .fade)
             self.refresh()
             self.tableView.reloadSections([0], with: .fade)
             ok.isEnabled = true
             }
          }
               
            else{
                OperationQueue.main.addOperation{
                var newValues = URLResourceValues()
                newValues.isHidden = true
                try! cell.videoURL?.setResourceValues(newValues)
                try! cell.urlJpg?.setResourceValues(newValues)
                self.refresh()
                self.tableView.reloadSections([0], with: .fade)
                self.refresh()
                self.tableView.reloadSections([0], with: .fade)
                ok.isEnabled = true
                }    
            }


I'll have to try it tomorrow as I'm away from the file. I'll update this post once I can.


BTW I had the background Queue because I thought it was the only way to basically separate what I wanted to happen and put it back into the main queue the way I wanted it orded. I had a brain ****.

That's the general idea, but the code duplication starts to look ugly. I'd be inclined to turn it inside out:


OperationQueue.main.addOperation {
     if UserDefaults.standard.integer(forKey: "numAllowed") == 3 {
          self.present(alert, animated: true, completion: {print ("it presented")})
     }
     var newValues = URLResourceValues() 
     newValues.isHidden = true 
     try! cell.videoURL?.setResourceValues(newValues) 
     try! cell.urlJpg?.setResourceValues(newValues) 
     self.refresh() 
     self.tableView.reloadSections([0], with: .fade) 
     self.refresh() 
     self.tableView.reloadSections([0], with: .fade) 
     ok.isEnabled = true 
}

But won't that "IF" possibly move to a background thread depending on the other processes happening then?

Why does the forum block the word 'f a r t'


hahahahahaha

That's not working. Meh. It seems as if the conditional is being assigned to a different thread. The presentation happens last.

Is there a way to print the list of processes in the queue?

Accepted Answer

Well, I lied to you a little bit. The "present" method is being executed in the correct order, but it has an asynchronous part that runs later. This means it's unpredictable when the presented view actually appears, from the point of view of the user.


If you want to strictly control the apparent order in which everything happens, you have to move your 'reload' code (lines 18-26 in the original post) into the completion handler of the "present" invocation — where you now have the print statement.


That means going back to code duplication, or — better — moving the reload code into a separate method, then calling that method from one of two places: inside the "present" completion handler, or instead of the "present" invocation.

Why didn't I think of that...I feel a little stupid. Another brain f.a.r.t. 😝


Thanks a ton again Quincey!

How to properly add operations to the main queue
 
 
Q