我创建了这个简单的示例来展示该问题。
我有 4 个按钮,用于点赞、收藏、举报和隐藏。用户可以随时点击任意按钮,点击任意次数。但是,在点击之后发送的网络请求之间必须始终有 2 秒的最小延迟。此外,所有请求都必须按顺序发送。
以下内容似乎可行,但有时,它不遵守 2 秒延迟并同时发送多个请求,中间没有任何延迟。我很难弄清楚是什么原因造成的。有一个更好的方法吗?或者说这有什么问题?
import UIKit
import SnapKit
extension UIControl {
func addAction(for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping()->()) {
addAction(UIAction { (action: UIAction) in closure() }, for: controlEvents)
}
}
let networkDelay = TimeInterval(2)
var requestWorkItems = [DispatchWorkItem]()
var lastRequestCompletionTime: Date?
class ViewController: UIViewController {
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.addAction { [weak self] in
let urlString = "https://example.com/api?action=\(title)"
self?.scheduleNetworkRequest(urlString: urlString)
}
button.titleLabel?.font = UIFont.systemFont(ofSize: 30, weight: .bold)
button.snp.makeConstraints { make in
make.height.equalTo(100)
}
stack.addArrangedSubview(button)
}
}
func scheduleNetworkRequest(urlString : String) {
let workItem = DispatchWorkItem { [weak self] in
self?.sendNetworkRequest(urlString: urlString)
}
requestWorkItems.append(workItem)
if Date().timeIntervalSince(lastRequestCompletionTime ?? .distantPast) > networkDelay {
scheduleNextWork(delay: 0)
}
}
func scheduleNextWork(delay : TimeInterval) {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
if let workItem = requestWorkItems.first {
requestWorkItems.removeFirst()
print("Tasks remaining: \(requestWorkItems.count)")
workItem.perform()
}
}
}
func sendNetworkRequest(urlString : String) {
guard let url = URL(string: urlString) else { return }
let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
if let error = error {
print("Error: \(error)")
} else if let _ = data {
print("Completed: \(urlString) at: \(Date())")
}
lastRequestCompletionTime = Date()
self?.scheduleNextWork(delay: networkDelay)
}
task.resume()
}
}
我会使用
AsyncStream
来记录 URL,并使用 Task
来消费流,中间等待 2 秒。
// add these properties to the view controller
var task: Task<Void, Never>?
let (stream, continuation) = AsyncStream.makeStream(of: URL.self)
在按钮的操作中,将 URL 添加到
continuation
:
button.addAction { [weak self] in
self?.continuation.yield(URL(string: "https://example.com/api?action=\(title)")!)
}
并在
task
中初始化 viewDidLoad
,如下所示:
task = Task {
for await url in stream {
do {
print("Sending request to \(url)")
let (data, response) = try await URLSession.shared.data(from: url)
// do something with data & response
// wait 2 seconds before getting the next URL
try await Task.sleep(for: .seconds(2))
} catch is CancellationError {
break
} catch {
// handle network errors...
}
if Task.isCancelled {
break
}
}
}
最后,在
task?.cancel()
中拨打deinit
。