5 Replies
      Latest reply: Jan 15, 2017 8:53 AM by eskimo RSS
      craigaps Level 1 Level 1 (0 points)

        Hi

         

        I have a couple of operations as follows:

         

        class Operation1: Operation
        {
            private var _executing: Bool = false
            private var _finished: Bool = false
        
            var hasError = false
          
            override internal(set) var isExecuting: Bool
            {
                get
                {
                    return _executing
                }
                set
                {
                    willChangeValue(forKey: "isExecuting")
                    _executing = newValue
                    didChangeValue(forKey: "isExecuting")
                }
            }
        
            override internal(set) var isFinished: Bool
            {
                get
                {
                    return _finished
                }
                set
                {
                    willChangeValue(forKey: "isFinished")
                    _finished = newValue
                    didChangeValue(forKey: "isFinished")
                }
            }
        
            /*
             Begins the execution of the operation.
             */
            override func main()
            {
                if isCancelled
                {
                    isFinished = true
                    return
                }
             
                isExecuting = true
             
                // Do something, but results in a failure
               self.hasError = true 
                self.finished()
            }
        
            /*
             Set the flag to signal a completion operation.
             */
            func finished()
            {
                self.isExecuting = false
                self.isFinished = true
            }
        }
        
        class Operation2: Operation
        {
            private var _executing: Bool = false
            private var _finished: Bool = false
        
            var hasError = false
          
            override internal(set) var isExecuting: Bool
            {
                get
                {
                    return _executing
                }
                set
                {
                    willChangeValue(forKey: "isExecuting")
                    _executing = newValue
                    didChangeValue(forKey: "isExecuting")
                }
            }
        
            override internal(set) var isFinished: Bool
            {
                get
                {
                    return _finished
                }
                set
                {
                    willChangeValue(forKey: "isFinished")
                    _finished = newValue
                    didChangeValue(forKey: "isFinished")
                }
            }
        
            /*
             Begins the execution of the operation.
             */
            override func main()
            {
                if isCancelled
                {
                    isFinished = true
                    return
                }
             
                isExecuting = true
             
                // Do something
                self.finished()
            }
        
            /*
             Set the flag to signal a completion operation.
             */
            func finished()
            {
                self.isExecuting = false
                self.isFinished = true
            }
        }
        

         

        In my view controller, I construct Operation1 and Operation2 making Operation2 a dendency of Operation1.  In the main function of Operation1, I set a property hasError (bool) if an error occurs.  The operation1.completionBlock calls cancelAllOperations() is operation1.hasError equal true.

         

        let queue = OperationQueue.main
                   
        let operation1 = Operation1()
        operation1.completionBlock = {
          if operation1.hasError
          {
             print("Failed, cancelling pending operations.")
             queue.cancelAllOperations()
          }
        }
                   
        let operation2 = Operation2()
        operation2.completionBlock = {
          DispatchQueue.main.async
          {
            // Do something on UI
          }
        }
                       
        operation2.addDependency(operation1)
        queue.addOperation(operation1)
        queue.addOperation(operation2)
        

         

        However, Operation2 continues to execute.  What is the best approach to cancel Operation2 if Operation1 fails?

         

        Thanks

        • Re: Cancel Dependent Operation in Operation.completionBlock
          eskimo Apple Staff Apple Staff (7,530 points)

          What is the best approach to cancel Operation2 if Operation1 fails?

          I usually just have Op2 look at the result of Op1 and fail if Op1 failed.  This makes sense, at least the way I look at it: if Op2 is dependent on Op1 then Op2 has to actually use the results of Op1 somehow.  If Op1’s result is missing, because it got cancelled or for any other reason, Op2 can’t proceed.

          Share and Enjoy

          Quinn “The Eskimo!”
          Apple Developer Relations, Developer Technical Support, Core OS/Hardware
          let myEmail = "eskimo" + "1" + "@apple.com"

            • Re: Cancel Dependent Operation in Operation.completionBlock
              craigaps Level 1 Level 1 (0 points)

              Hi eskimo

               

              Thanks for the reply.  Do you have an example of how you explained this.  I thought the queue.cancelAllOperations() calls cancel() on operations in the queue, which signals the finished = true on any executing operations i.e operation2.  Is there a reason why you can't use the completionBlock of operation1 to call cancelAllOperations()?

               

              I'm calling constructing the queue and adding the operations for each cell:

               

              func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

               

               

               

              Thanks again

                • Re: Cancel Dependent Operation in Operation.completionBlock
                  eskimo Apple Staff Apple Staff (7,530 points)

                  Do you have an example of how you explained this.

                  Sure.  But perhaps first you can explain why your operations are dependent.  What about Op1 does Op2 depend on?

                  For example, one case I’ve used in the past in a JSON parser operation (Op2) that’s dependent on an HTTP GET operation (Op1).  The JSON parser grabs the HTTP data from the HTTP GET operation, and thus it’s easy for it to determine if it was cancelled (or failed for some other reason).

                  ps These days I don’t have them so tightly coupled, but instead use the technique I outlined in this post.

                  Share and Enjoy

                  Quinn “The Eskimo!”
                  Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                  let myEmail = "eskimo" + "1" + "@apple.com"

                    • Re: Cancel Dependent Operation in Operation.completionBlock
                      craigaps Level 1 Level 1 (0 points)

                      So I ended up handling the cancel in operation2 by overriding isCancelled

                       

                      override internal(set) var isCancelled: Bool
                      {
                        get
                        {
                          return _cancelled
                        }
                        set
                        {
                          willChangeValue(forKey: "isCancelled")
                          _cancelled = newValue
                          didChangeValue(forKey: "isCancelled")
                        }
                      }
                      
                      override var isAsynchronous: Bool
                      {
                        return true
                      }
                      
                      override func cancel()
                      {
                        print("Operation cancelled.")
                        _cancelled = true
                      }
                      

                       

                      So now the cellForRowAt function looks like this:

                       

                      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
                      {
                        let cell = tableView.dequeueReusableCell(withIdentifier: "AccountCell", for: indexPath)
                        let operation1 = Operation1()
                        operation1.completionBlock = {
                            DispatchQueue.main.async
                            {
                                if (operation1?.hasError)!
                                {
                                    operation2.cancel()
                                }
                            }
                       }
                      
                      
                      let operation2 = Operation2()      
                      operation2.completionBlock = {
                        DispatchQueue.main.async
                        {
                          // Do something on UI
                          cell.textLabel.text = operation2.name
                        }
                      let queue = OperationQueue.main}
                      
                      operation2.addDependency(operation1)
                      queue.addOperation(operation1)
                      queue.addOperation(operation2)
                      
                              return cell
                      }
                      
                      

                      This approach seems to work ok.  With multiple rows in the table, I'm noticing that the OperationQueue.main (for each row) is not executing asynchronously.  If that because I'm using OperationQueue.main?  If so, how do I go about enabling the OperationQueue async?

                       

                      Thanks again

                        • Re: Cancel Dependent Operation in Operation.completionBlock
                          eskimo Apple Staff Apple Staff (7,530 points)

                          With multiple rows in the table, I'm noticing that the OperationQueue.main (for each row) is not executing asynchronously. If that because I'm using OperationQueue.main?

                          I presume you mean “because I’m scheduling the operations on the main queue?”, in which case the answer is yes.  If you schedule an operation on the main queue then the main() function of that operation runs on the main thread, which isn’t helpful if you’re trying to get it to run in parallel.

                          To address this you’ll need to create your own operation queue and schedule your operations on that.  In many cases you can use a global (or static) variable for this, because it’s fine for the queue to be long-lived.

                          Also, you can avoid the dispatch async to the main thread by creating a third operation.

                          let op1 = … as before …
                          let op2 = … as before …
                          let op3 = BlockOperation {
                              … main thread stuff …
                          }
                          
                          op2.addDependency(op1)
                          op3.addDependency(op2)
                          
                          queue.addOperation(op1)
                          queue.addOperation(op2)
                          queue.addOperation(op3)
                          

                          Share and Enjoy

                          Quinn “The Eskimo!”
                          Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                          let myEmail = "eskimo" + "1" + "@apple.com"