Instead of semaphores or groups that others have reported (which blocks a thread, which can be problematic if you have too many threads blocked), I would use a custom asynchronous subclass of NSOperation for network requests. After you wrapped the request in asynchronous NSOperation , you can add a bunch of operations to the operation queue without blocking the threads, but enjoying the dependencies between these asynchronous operations.
For example, a network operation might look like this:
class NetworkOperation: AsynchronousOperation { private let url: NSURL private var requestCompletionHandler: ((NSData?, NSURLResponse?, NSError?) -> ())? private var task: NSURLSessionTask? init(url: NSURL, requestCompletionHandler: (NSData?, NSURLResponse?, NSError?) -> ()) { self.url = url self.requestCompletionHandler = requestCompletionHandler super.init() } override func main() { task = NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in self.requestCompletionHandler?(data, response, error) self.requestCompletionHandler = nil self.completeOperation() } task?.resume() } override func cancel() { requestCompletionHandler = nil super.cancel() task?.cancel() } } /// Asynchronous Operation base class /// /// This class performs all of the necessary KVN of `isFinished` and /// `isExecuting` for a concurrent `NSOperation` subclass. So, to developer /// a concurrent NSOperation subclass, you instead subclass this class which: /// /// - must override `main()` with the tasks that initiate the asynchronous task; /// /// - must call `completeOperation()` function when the asynchronous task is done; /// /// - optionally, periodically check `self.cancelled` status, performing any clean-up /// necessary and then ensuring that `completeOperation()` is called; or /// override `cancel` method, calling `super.cancel()` and then cleaning-up /// and ensuring `completeOperation()` is called. public class AsynchronousOperation : NSOperation { override public var asynchronous: Bool { return true } private let stateLock = NSLock() private var _executing: Bool = false override private(set) public var executing: Bool { get { return stateLock.withCriticalScope { _executing } } set { willChangeValueForKey("isExecuting") stateLock.withCriticalScope { _executing = newValue } didChangeValueForKey("isExecuting") } } private var _finished: Bool = false override private(set) public var finished: Bool { get { return stateLock.withCriticalScope { _finished } } set { willChangeValueForKey("isFinished") stateLock.withCriticalScope { _finished = newValue } didChangeValueForKey("isFinished") } } /// Complete the operation /// /// This will result in the appropriate KVN of isFinished and isExecuting public func completeOperation() { if executing { executing = false finished = true } } override public func start() { if cancelled { finished = true return } executing = true main() } } // this locking technique taken from "Advanced NSOperations", WWDC 2015 // https://developer.apple.com/videos/play/wwdc2015/226/ extension NSLock { func withCriticalScope<T>(@noescape block: Void -> T) -> T { lock() let value = block() unlock() return value } }
Having done this, you can initiate a series of requests that can be executed sequentially:
let queue = NSOperationQueue() queue.maxConcurrentOperationCount = 1 for urlString in urlStrings { let url = NSURL(string: urlString)! print("queuing \(url.lastPathComponent)") let operation = NetworkOperation(url: url) { data, response, error in
Or, if you do not want to suffer from a significant decrease in the performance of consecutive requests, but still want to limit the degree of concurrency (to minimize system resources, avoid timeouts, etc.), you can set maxConcurrentOperationCount to a value of 3 or 4.
Or you can use dependencies, for example, to start a process when all asynchronous downloads are executed:
let queue = NSOperationQueue() queue.maxConcurrentOperationCount = 3 let completionOperation = NSBlockOperation() { self.tableView.reloadData() } for urlString in urlStrings { let url = NSURL(string: urlString)! print("queuing \(url.lastPathComponent)") let operation = NetworkOperation(url: url) { data, response, error in
And if you want to cancel requests, you can easily cancel them:
queue.cancelAllOperations()
Operations is an incredibly rich mechanism for managing a series of asynchronous tasks. If you refer to the WWDC 2015 Advanced NSOperations video , they took this template to a completely different level with conditions and observers (although their solution may be a bit reworked for simple tasks. IMHO).