You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift实现点击触发网络请求:保证请求间隔2秒且顺序执行

问题分析与解决方案

原代码的核心问题

  1. 全局变量的线程竞态:requestWorkItems和lastRequestCompletionTime是全局变量,UI点击(主线程)和网络请求回调(后台线程)会同时操作这些变量,导致判断逻辑出错。比如快速连续点击按钮时,多个scheduleNetworkRequest会同时检查lastRequestCompletionTime,都认为可以立即执行,进而多次触发scheduleNextWork(delay:0),最终多个请求被同时发送。

  2. 队列操作无同步保护:对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()
    }
}

优化点说明

  1. 线程安全的任务管理:所有对requestQueue和isProcessing的操作都在串行队列taskQueue中执行,彻底避免多线程竞态问题。
  2. 状态标记控制流程:用isProcessing标记是否正在处理请求,确保同一时间只有一个请求在执行,避免重复触发任务调度。
  3. 清晰的流程分离:enqueueRequest负责添加任务,processNextRequest负责调度执行,sendNetworkRequest专注网络请求,职责更清晰。
  4. 避免全局变量:把所有状态变量移到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 06:05:02