Swift实现点击触发网络请求:保证请求间隔2秒且顺序执行
问题分析与解决方案
原代码的核心问题
全局变量的线程竞态:
requestWorkItems和lastRequestCompletionTime是全局变量,UI点击(主线程)和网络请求回调(后台线程)会同时操作这些变量,导致判断逻辑出错。比如快速连续点击按钮时,多个scheduleNetworkRequest会同时检查lastRequestCompletionTime,都认为可以立即执行,进而多次触发scheduleNextWork(delay:0),最终多个请求被同时发送。队列操作无同步保护:对
requestWorkItems的添加、移除操作没有加锁或在串行队列中执行,多线程环境下可能出现数组越界、重复取任务的情况。
修复实现(基于串行队列的线程安全方案)
我们可以用一个串行调度队列来管理所有任务的添加和调度逻辑,避免多线程竞态;同时把状态变量移到ViewController内部,避免全局变量的线程安全问题:
import UIKit import SnapKit extension UIControl { func addAction(for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping()->()) { addAction(UIAction { _ in closure() }, for: controlEvents) } } class ViewController: UIViewController { // 串行队列,保证所有任务调度逻辑线程安全 private let taskQueue = DispatchQueue(label: "com.example.requestQueue") private let minimumInterval: TimeInterval = 2 private var requestQueue: [String] = [] private var isProcessing: Bool = false override func viewDidLoad() { super.viewDidLoad() let stack = UIStackView() stack.axis = .vertical view.addSubview(stack) stack.snp.makeConstraints { make in make.center.equalToSuperview() } ["UPVOTE","FAVORITE","FLAG","HIDE"].forEach { title in let button = UIButton() button.setTitle(title, for: .normal) button.setTitleColor(.blue, for: .normal) button.addAction { [weak self] in let urlString = "https://example.com/api?action=\(title)" self?.enqueueRequest(urlString: urlString) } button.titleLabel?.font = UIFont.systemFont(ofSize: 30, weight: .bold) button.snp.makeConstraints { make in make.height.equalTo(100) } stack.addArrangedSubview(button) } } private func enqueueRequest(urlString: String) { taskQueue.async { [weak self] in guard let self = self else { return } self.requestQueue.append(urlString) // 如果当前没有在处理任务,立即启动处理流程 if !self.isProcessing { self.processNextRequest() } } } private func processNextRequest() { taskQueue.async { [weak self] in guard let self = self, let nextUrl = self.requestQueue.first else { return } self.isProcessing = true self.requestQueue.removeFirst() // 切换到全局队列发送网络请求,避免阻塞串行队列 DispatchQueue.global().async { self.sendNetworkRequest(urlString: nextUrl) { [weak self] in // 请求完成后,延迟指定时间再处理下一个任务 self?.taskQueue.asyncAfter(deadline: .now() + self?.minimumInterval ?? 0) { self?.isProcessing = false self?.processNextRequest() } } } } } private func sendNetworkRequest(urlString: String, completion: @escaping ()->()) { guard let url = URL(string: urlString) else { completion() return } let task = URLSession.shared.dataTask(with: url) { data, response, error in if let error = error { print("Error: \(error)") } else if let _ = data { print("Completed: \(urlString) at: \(Date())") } completion() } task.resume() } }
优化点说明
- 线程安全的任务管理:所有对
requestQueue和isProcessing的操作都在串行队列taskQueue中执行,彻底避免多线程竞态问题。 - 状态标记控制流程:用
isProcessing标记是否正在处理请求,确保同一时间只有一个请求在执行,避免重复触发任务调度。 - 清晰的流程分离:
enqueueRequest负责添加任务,processNextRequest负责调度执行,sendNetworkRequest专注网络请求,职责更清晰。 - 避免全局变量:把所有状态变量移到ViewController内部,降低耦合,提高代码可维护性。
更优雅的方案:使用OperationQueue
如果想进一步简化代码,可以用OperationQueue来管理请求,通过设置队列最大并发数为1,再给每个操作添加延迟依赖:
import UIKit import SnapKit extension UIControl { func addAction(for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping()->()) { addAction(UIAction { _ in closure() }, for: controlEvents) } } class ViewController: UIViewController { private let minimumInterval: TimeInterval = 2 private let operationQueue: OperationQueue = { let queue = OperationQueue() queue.maxConcurrentOperationCount = 1 // 串行执行 return queue }() private var lastOperation: Operation? override func viewDidLoad() { super.viewDidLoad() let stack = UIStackView() stack.axis = .vertical view.addSubview(stack) stack.snp.makeConstraints { make in make.center.equalToSuperview() } ["UPVOTE","FAVORITE","FLAG","HIDE"].forEach { title in let button = UIButton() button.setTitle(title, for: .normal) button.setTitleColor(.blue, for: .normal) button.addAction { [weak self] in let urlString = "https://example.com/api?action=\(title)" self?.addRequestOperation(urlString: urlString) } button.titleLabel?.font = UIFont.systemFont(ofSize: 30, weight: .bold) button.snp.makeConstraints { make in make.height.equalTo(100) } stack.addArrangedSubview(button) } } private func addRequestOperation(urlString: String) { let requestOp = BlockOperation { [weak self] in self?.sendNetworkRequest(urlString: urlString) } // 如果上一个操作存在,添加延迟依赖 if let lastOp = lastOperation { let delayOp = BlockOperation { Thread.sleep(forTimeInterval: self.minimumInterval) } delayOp.addDependency(lastOp) requestOp.addDependency(delayOp) operationQueue.addOperation(delayOp) } operationQueue.addOperation(requestOp) lastOperation = requestOp } private func sendNetworkRequest(urlString: String) { guard let url = URL(string: urlString) else { return } let semaphore = DispatchSemaphore(value: 0) let task = URLSession.shared.dataTask(with: url) { data, response, error in if let error = error { print("Error: \(error)") } else if let _ = data { print("Completed: \(urlString) at: \(Date())") } semaphore.signal() } task.resume() semaphore.wait() // 阻塞操作直到请求完成 } }
这个方案利用Operation的依赖机制,自动保证请求顺序和间隔,代码更简洁,是Apple官方推荐的复杂任务流管理方式。
内容的提问来源于stack exchange,提问作者sudoExclamationExclamation
相关产品推荐
相关产品推荐

