Вместо семафоров или групп, которые другие сообщили (что блокирует поток, что может быть проблематичным, если у вас слишком много потоков заблокировано), я бы использовал пользовательский асинхронный подкласс класса NSOperation
для сетевых запросов.После того как вы завернули запрос в асинхронном NSOperation
, вы можете добавить кучу операций в очередь операций, не блокируя нити, но наслаждаясь зависимостями между этими асинхронными операциями.
Например, работа сети может выглядеть следующим образом:
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
}
}
Сделав это, вы можете инициировать целый ряд запросов, которые могут быть выполнены последовательно:
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
// do something with the `data`
}
queue.addOperation(operation)
}
Или, если вы не хотят страдать от значительного снижения производительности последовательных запросов, но все же хотите ограничить степень параллелизма (чтобы свести к минимуму системные ресурсы, избежать тайм-аутов и т. д.), вы можете установить maxConcurrentOperationCount
на значение, подобное 3 или 4.
Или, вы можете использовать зависимости, например, чтобы вызвать некоторый процесс, когда все асинхронной загрузки сделаны:
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
// do something with the `data`
}
queue.addOperation(operation)
completionOperation.addDependency(operation)
}
// now that they're all queued, you can queue the completion operation on the main queue, which will only start once the requests are done
NSOperationQueue.mainQueue().addOperation(completionOperation)
И если вы хотите, чтобы отменить запросы, вы можете легко их отменить:
queue.cancelAllOperations()
Операции являются невероятно богатым механизмом для управления серией асинхронных задач. Если вы ссылаетесь на видео WWDC 2015 Advanced NSOperations, они взяли этот шаблон на совершенно другой уровень с условиями и наблюдателями (хотя их решение может быть немного перенастроено для простых задач. IMHO).
http://stackoverflow.com/a/28169498 – Shubhank
Если у вас есть порядок зависимости между MethodOne и MethodTwo, почему вы используете две отдельные асинхронные операции? – TJA
Взгляните на этот очень похожий вопрос и ответ, который я дал недавно: http://stackoverflow.com/a/37155037/465677 – CouchDeveloper