1

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?

Community
  • 1
  • 1
Taco
  • 701
  • 7
  • 21

1 Answers1

7

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.

Community
  • 1
  • 1
mz2
  • 4,672
  • 1
  • 27
  • 47
  • This works and again a clear explanation! I also agree that the operation queue shouldn’t use the main queue. However, if `OperationQueue.main` is used anyway, I would expect a UI block, but it appears that the completionHandler of Alamofire isn’t called at all (and thus the semaphore is not signaled). Although I will not implement it in this way, I would appreciate to understand why it doesn’t work… – Taco Oct 10 '16 at 08:49
  • The reason the callback isn't fired if you use `OperationQueue.main` to execute the block operation above, the semaphore `wait` call is made inside the block operation and will block the main queue. If the timeout wasn't there, in that scenario this would lead to a deadlock. There is another ugly subtle bug too possible with this semaphore based route: if the timeout period in my above case is shorter than that of NSURLSession / NSURLRequest executing the request, the block operation finishes and then the callback would be fired sometime later. I stay away from semaphores when I can :) – mz2 Oct 10 '16 at 09:14