如何在Swift异步代码中实现内容逐行依次显示?
问题分析与解决方案
你的代码里所有内容一次性显示的核心原因是主线程被信号量阻塞了:viewDidLoad在主线程执行,每次调用run时的semaphore.wait()会把主线程死死卡住,直到后台队列的asyncAfter任务调用signal。而你在asyncAfter里用DispatchQueue.main.async提交的UI更新闭包,此时根本没法在被阻塞的主线程上执行,只能全部排队等着。等10次循环跑完主线程终于解放,所有排队的UI更新才会一次性执行,所以你看到所有内容瞬间出现。
下面给你几个可行的解决方案,都能实现内容逐行依次显示:
方案1:递归异步调用(最直观,不阻塞主线程)
把for循环改成递归逻辑,每次完成一个异步任务并更新UI后,再启动下一个任务,完全避免主线程阻塞:
import Cocoa class ViewController: NSViewController { private let concurrentQueue = DispatchQueue.global() private var currentIndex = 1 private let maxCount = 10 override func viewDidLoad() { super.viewDidLoad() startNextTask() // 启动第一个任务 } func startNextTask() { guard currentIndex <= maxCount else { return } let i = currentIndex let seed = arc4random() let isSuccess = seed % 5 == 1 let delayInSeconds = Int(seed % 3) concurrentQueue.asyncAfter(wallDeadline: .now() + .seconds(delayInSeconds)) { DispatchQueue.main.async { self.textView.string += "\(i)\t\(seed)\n" // 更新完UI后再判断是否启动下一个任务 self.currentIndex += 1 if !isSuccess { self.startNextTask() } } } } @IBOutlet var textView: NSTextView! }
这个方案逻辑清晰,每个任务完成后才会触发下一个,UI能及时响应更新,完全不会卡主线程。
方案2:Swift 5.5+ 用结构化并发(现代写法)
如果你的项目使用Swift 5.5及以上版本,用Task和异步循环的写法会更简洁优雅:
import Cocoa class ViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() Task { for i in 1...10 { let seed = arc4random() let isSuccess = seed % 5 == 1 let delayInSeconds = Int(seed % 3) // 等待延迟,不会阻塞任何线程 try await Task.sleep(nanoseconds: UInt64(delayInSeconds) * 1_000_000_000) // 回到主线程更新UI await MainActor.run { self.textView.string += "\(i)\t\(seed)\n" } if isSuccess { break } } } } @IBOutlet var textView: NSTextView! }
Task.sleep会挂起当前异步任务而不阻塞线程,MainActor.run确保UI更新在主线程执行,代码可读性拉满,是现在Swift异步编程的首选方式。
内容的提问来源于stack exchange,提问作者Owen Zhao
相关产品推荐
相关产品推荐

