我会使用自定义的异步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,他们已经通过条件和观察者将这种模式提升到了一个全新的水平(尽管他们的解决方案对于简单的问题可能有点过度设计。恕我直言)。