Retry Operations

Hi


The sample code below I lifted from another another post, let's go with this example to describe what I'm trying to do.

https://forums.developer.apple.com/message/86598#


Let's say fetchOp fails due to an authentication error. I want to kick off another operation to re-authenticate, then retry the fetchOp. What's would be the best approach for achieving this?


class FetchOperation: NSOperation {  
    var url: NSURL?  
    var data: NSData?  
    var error: NSError?  

    convenience init(url: NSURL) {  
        self.init()  
        self.url = url  
    }  

    // I've omitted the intricacies of implementing an async  
    // network operation because that’s too complex a topic to  
    // cover here.  The only thing to watch out for is that, for  
    // adapter operations to work in a fully composible fashion,  
    // the start of the async code must check self.error and fail  
    // immediately if it’s set.  
}  

class ParseOperation: NSOperation {  
    var data: NSData?  
    var error: NSError?  

    convenience init(data: NSData) {  
        self.init()  
        self.data = data  
    }  

    override func main() {  
        if self.error == nil {  
            if let data = self.data {  
                self.parseData(data)  
            } else {  
                // We have no data to parse; this shouldn't happen.  
                fatalError()  
            }  
        }  
    }  

    func parseData(data: NSData) {  
        // ... parse the data ...  
    }  
}  

let fetchOp = FetchOperation(url: url)  
let parseOp = ParseOperation()  
let adapterOp = NSBlockOperation(block: {  
    parseOp.data  = fetchOp.data  
    parseOp.error = fetchOp.error  
})  

adapterOp.addDependency(fetchOp)  
parseOp.addDependency(adapterOp)  

networkQueue.addOperation(fetchOp)  
computeQueue.addOperation(adapterOp)  
computeQueue.addOperation(parseOp) 

Many thanks

I managed to get this working, here's my implementation:


var token = UserDefaults.standard.value(forKey: "token") as! Token   // Just a placeholder for demo purposes

// Create the queue.
            let queue = OperationQueue()
          
            // Create operations to check for pending data.
            let operation1 = CheckPendingOperation()
            operation1.token = token
            operation1.completionBlock =
            {
                if let error = operation1.error as? NetworkError
                {
                    if case .unauthenticated = error
                    {
                        // Failed with an unauthenticated, attempt a OAuth token refresh
                        let operation2 = RefreshTokenOperation()
                        operation2.token = token
                        let adpater = BlockOperation
                        {
                            // Recreate the check for pending data with the new token.
                            let operation3 = CheckPendingOperation()
                            token = operation2.token!
                            
                            operation3.token = token
                            operation3.completionBlock =
                            {
                                // Refresh has been completed, update the token store.
                                UserDefaults.standard.set(token, forKey: "token")
                                
                                // Update the visual cue of the cell.
                                DispatchQueue.main.async
                                {
                                    cell.hasPending = operation3.pendingCount > 0
                                }
                            }
                            operation3.addDependency(operation2)
                            queue.addOperation(operation3)

                        }
                        adpater.addDependency(operation2)
                        queue.addOperations([operation2, adpater], waitUntilFinished: true)
                    }
                }
                else
                {
                    // Update the visual cue of the cell.
                    DispatchQueue.main.async
                    {
                        cell.hasPending = operation1.pendingCount > 0
                    }
                }
            }
            
            queue.addOperation(operation1)
        }


Without getting into the nuts and bolts of the but the CheckPendingOperation has a token and pendingCount properties which are passed between operations using the BlockOperation. It's the pendingCount with get displayed against a UITableCell object.


If anyone has any suggestions to improve this code, I'd been keen to hear them, maybe a Group Operation??


Thanks

The way I’ve implemented this in the past is to implement a

RetryOperation
wrapper around
FetchOperation
that applies a retry policy. This is a ‘has a’ relationship, that is, the retry operation is a concurrent operation thet has a fetch operation and, when that fetch operation completes, it checks the result and if it indicates an error that warrants retrying it does whatever is necessary to resolve that problem and then creates a new fetch operation for the retry.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hey eskimo


Do you have an example of the RetryOperation?


Thanks 🙂

Do you have an example of the RetryOperation?

Nothing that’s both modern and public. The best I have to offer is the RetryingHTTPOperation class in the old MVCNetworking sample code. While that code is probably not useful to you, the basic ideas it illustrates are still sound.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"


WWDC runs Mon, 4 Jun through to Fri, 8 Jun. During that time all of DTS will be at the conference, helping folks out face-to-face.
Retry Operations
 
 
Q