Victor Ronin
Victor Ronin

Reputation: 23298

Is there a way to do Alamofire requests with retries

I have a lot of places in the code where Alamofire request/response are handled.

Each of this requests may fail because of some intermittent problem (the most common is flaky network).

I would like to be able to retry requests 3 times before bailing out.

The straightforward method would be to having something like that

var errorCount = 0
func requestType1() {
   let request = Alamofire.request(...).responseJSON { response in
       if (isError(response) && errorCount < 3) {
          errorCount += 1
          request1()
       } 
       if (isError(response)) {
          handleError()
       }

       handleSuccess()
   }
}

However, I dislike this approach A LOT for multiple reasons. The most obvious is that I will need to implement such code for each request type (and I have something like 15 of them).

I am curios whether there is way to do something like (where the changes are minimal and non intrusive)

let request = Alamofire.request(..., **3**) 

Upvotes: 13

Views: 18778

Answers (5)

Suhit Patil
Suhit Patil

Reputation: 12023

Alamofire 5 and Above

Alamofire provides an inbuilt class RetryPolicy which confirms to RequestRetrier protocol. RetryPolicy provides a default implementation to retry requests which failed due to system errors, such as network connectivity. source

Set the RetryPolicy while creating the Alamofire.Session object in the NetworkClient

class NetworkClient {
    
    private let session: Alamofire.Session = {
        let session = Session(interceptor: RetryPolicy())
        return session
    }()

    ....

}

Upvotes: 1

Daljeet Seera
Daljeet Seera

Reputation: 198

I've created one wrapper class for request retrier should function. https://gist.github.com/daljeetseera/7ce2b53b8a88d8a5e9b172c0495c6455

And used the request retrier in session manager required for request.

static let sharedManager: SessionManager = {
        let configuration = URLSessionConfiguration.default
        let manager = Alamofire.SessionManager(configuration: configuration)
        let requestRet = NetworkRequestRetrier()
        manager.retrier = requestRet
        return manager
    }()

Upvotes: 1

Carla Camargo
Carla Camargo

Reputation: 582

I've had the same problem, and I got the requests to be retried using the RequestRetrier, should method and request.retryCount. Something like it:

// MARK: - RequestRetry

public func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
    lock.lock() ; defer { lock.unlock() }


    if let response = request.task?.response as? HTTPURLResponse{
        if response.statusCode == 401 {
            requestsToRetry.append(completion)

            getToken { (expires, _) in
               _ = SessionCountdownToken.sharedInstance.startCount(expirationTime: expires)
            }
        } else {

            if request.retryCount == 3 { completion(false, 0.0 ); return}
            completion(true, 1.0)
            return
        }
    } else {
        completion(false, 0.0)
    }
}

Upvotes: 5

iwasrobbed
iwasrobbed

Reputation: 46713

Alamofire 4.0 has a RequestRetrier protocol you can use.

https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%204.0%20Migration%20Guide.md#request-retrier

Example:

class OAuth2Handler: RequestAdapter, RequestRetrier {
    public func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: RequestRetryCompletion) {
        if let response = request.task.response as? HTTPURLResponse, response.statusCode == 401 {
            completion(true, 1.0) // retry after 1 second
        } else {
            completion(false, 0.0) // don't retry
        }

        // Or do something with the retryCount
        // i.e. completion(request.retryCount <= 10, 1.0)
    }
}

let sessionManager = SessionManager()
sessionManager.retrier = OAuth2Handler()

sessionManager.request(urlString).responseJSON { response in
    debugPrint(response)
}

Upvotes: 24

Carlos
Carlos

Reputation: 6021

One of the bits of syntactic sugar you get with Swift is you can use this:

public func updateEvents(someNormalParam: Bool = true, someBlock: (Void->Void))

Like this:

updateEvents(someNormalParam: false) {...}

Note the block is outside the () of the updateEvents function, contrary to where you'd normally expect it. It works only if the block is the last thing in the declaration of the function.

That means if you happen to have a block such as your Alamofire request, you can effectively wrap it with your retry functionality. One slightly complicating issue is you want to call a block within the block. Not a big deal:

func retryWrapper(alamoBlock: (Void->Request)) {
   alamoblock().responseJSON() {
       //Your retry logic here
   }
}

And you use it like so:

retryWrapper() {
    Alamofire.request(method, targetUrl, parameters: parameters, encoding: encoding)
}

Meaning all you have to do is find your Alamofire calls and wrap them in { } and put retryWrapper() before. The retry logic itself is only there once.

Upvotes: 3

Related Questions