Search code examples
swiftalamofirensoperationqueue

How is it possible to perform multiple Alamofire requests that are finished one after another?


I would like to perform multiple Alamofire requests. However, because of data dependency a new request should only start when the previous is finished.

I already asked a question with a more general example of an asynchronous request which was solved with OperationQueue. However, I do not succeed to achieve the same with Alamofire.

public func performAlamofireRequest(_ number: Int, success: @escaping (Int) -> Void)->Void {
    Alamofire.request(String(format: "http://jsonplaceholder.typicode.com/posts/%i", number+1)) // NSURLSession dispatch queue
        .responseString { response in // Completion handler at main dispatch queue? 
            if response.result.isSuccess {
             //   print("data")
            } else if response.result.isFailure {
             //   print("error")
            }
            success(number) // Always leave closure in this example
    }
}

To assure that requests are finished before a next request is started, I use OperationQueue as follows:

let operationQueue = OperationQueue.main
for operationNumber in 0..<4 { // Create some operations
    let operation = BlockOperation(block: {
        performAlamofireRequest(operationNumber) { number in
            print("Operation #\(number) finished")
    }
})
operation.name = "Operation #\(operationNumber)"

    if operationNumber > 0 {
        operation.addDependency(operationQueue.operations.last!)
    }
    operationQueue.addOperation(operation)
}

However, the output is:

Operation #0 finished
Operation #3 finished
Operation #2 finished
Operation #1 finished

which is clearly not correct.

How would it be possible to achieve this with Alamofire?


Solution

  • The issue is just the same as in the related question you posed: the operation dependencies are on finishing an operation, as documented, but you have written code where the operation exits after asynchronously dispatching a request for future execution (the operations you created and added to a queue will finish in the order set by their dependencies, but the requests will be fired concurrently by the NSURLSession underlying Alamofire).

    If you need serial execution, you can for instance do the following:

    // you should create an operation queue, not use OperationQueue.main here –
    // synchronous network IO that would end up waiting on main queue is a real bad idea.
    let operationQueue = OperationQueue()
    let timeout:TimeInterval = 30.0
    
    for operationNumber in 0..<4 {
        let operation = BlockOperation {
            let s = DispatchSemaphore(value: 0)
            self.performAlamofireRequest(operationNumber) { number in
                // do stuff with the response.
                s.signal()
            }
    
            // the timeout here is really an extra safety measure – the request itself should time out and end up firing the completion handler.
            s.wait(timeout: DispatchTime(DispatchTime.now, Int64(timeout * Double(NSEC_PER_SEC))))
        }
    
        operationQueue.addOperation(operation)
    }
    

    Various other solutions are discussed in connection to this question, arguably a duplicate. There's also Alamofire-Synchronous.