Swift中OperationQueue:限制同时2个异步请求,等待完成再启动下一个
解决Swift OperationQueue异步任务并发控制失效的问题
嘿,这个坑我之前踩过!问题根源在于普通的Operation默认是同步执行的——你的代码里,URLSession的下载任务一提交就立刻返回了,Operation会误以为自己已经执行完成,所以OperationQueue会不断往队列里塞新操作,完全无视你设置的maxConcurrentOperationCount = 2限制。
要解决这个问题,你需要自定义一个异步Operation,手动管理它的执行状态,让OperationQueue能准确识别任务是否真的完成。下面是具体的实现步骤:
1. 自定义异步下载Operation
我们需要重写Operation的几个关键属性和方法,来正确报告异步任务的执行状态:
class AsyncImageDownloadOperation: Operation { private let urlString: String private var task: URLSessionTask? // 可以添加闭包回调下载结果 var completion: ((UIImage?) -> Void)? // 必须重写这两个属性,并支持KVO,让队列能感知状态变化 private var _isExecuting = false override var isExecuting: Bool { get { return _isExecuting } set { willChangeValue(forKey: "isExecuting") _isExecuting = newValue didChangeValue(forKey: "isExecuting") } } private var _isFinished = false override var isFinished: Bool { get { return _isFinished } set { willChangeValue(forKey: "isFinished") _isFinished = newValue didChangeValue(forKey: "isFinished") } } // 标记这是异步操作 override var isAsynchronous: Bool { return true } init(urlString: String) { self.urlString = urlString super.init() } override func start() { // 如果操作已取消,直接标记完成 if isCancelled { isFinished = true return } isExecuting = true guard let url = URL(string: urlString) else { finish() return } let request = URLRequest(url: url) let session = URLSession(configuration: .default) task = session.dataTask(with: request) { [weak self] data, response, error in defer { self?.finish() } guard let self = self, !self.isCancelled else { return } // 处理下载结果,转成UIImage let image = data.flatMap(UIImage.init) // 回到主线程回调结果(如果需要更新UI) DispatchQueue.main.async { self.completion?(image) } } task?.resume() } override func cancel() { super.cancel() task?.cancel() finish() } private func finish() { isExecuting = false isFinished = true } }
2. 使用自定义Operation控制并发
现在用这个自定义操作替代原来的逻辑,加入OperationQueue后,并发限制就会生效了:
let imageQueue = OperationQueue() imageQueue.maxConcurrentOperationCount = 2 // 现在这个设置会正确生效 // 假设你有一堆需要加载的图片URL let imageUrls = [ "https://example.com/image1.jpg", "https://example.com/image2.jpg", "https://example.com/image3.jpg", "https://example.com/image4.jpg" ] for url in imageUrls { let operation = AsyncImageDownloadOperation(urlString: url) operation.completion = { [weak self] image in // 这里拿到图片后,更新对应的tableView cell if let image = image { print("图片加载完成:\(url)") // self?.updateCellWithImage(image, forUrl: url) } } imageQueue.addOperation(operation) }
为什么这样能解决问题?
- 我们重写了
isExecuting和isFinished属性并实现KVO通知,OperationQueue会通过这两个属性精准判断操作的真实状态。 - 只有当下载任务真正完成(或被取消)时,我们才会把
isFinished设为true,此时队列才会移除当前操作,再启动下一个操作。 - 重写
isAsynchronous明确告诉系统这是异步操作,避免队列误判任务完成时机。
额外小提示
- 记得在
cancel()方法里取消URLSessionTask,避免不必要的网络请求和资源浪费。 - 如果需要复用URLSession,可以把session实例抽出来,不用每次创建新的session。
内容的提问来源于stack exchange,提问作者Trangler
相关产品推荐
相关产品推荐

