SwiftUI中使用Alamofire downloadProgress时UI锁死如何解决
SwiftUI 结合 Alamofire 下载进度更新导致UI无响应解决方案
问题根因
你下载的是10GB大体积测试文件,Alamofire的downloadProgress回调触发频率极高,每秒可达数十次。每次回调都强制切主线程更新@Published属性,会触发SwiftUI高频重绘整个视图层级,直接占满主线程的事件处理队列,导致用户点击等交互事件无法被及时响应,最终表现为弹窗无法关闭、UI整体锁死。
你之前把回调放到独立后台队列的操作只能避免后台计算占用主线程,没有解决高频主线程更新的核心问题,所以无效。
修复方案
核心思路是对进度更新做降频处理,过滤掉用户感知不到的高频更新,仅在进度变化达到可感知阈值、或者固定时间间隔内最多更新一次UI即可。
这里提供两种可直接落地的实现方式:
方案1:手动判断进度阈值(最简单)
仅当进度变化超过1%时才更新UI,大幅减少主线程更新次数,修改后的ViewModel代码如下:
import Combine import SwiftUI import Alamofire class ViewModel: ObservableObject { @Published var currentDownload: DownloadRequest? = nil @Published var downloadProgress: Double = 0.0 @Published var showProgressView: Bool = false // 记录上一次UI更新的进度值 private var lastUpdatedProgress: Double = 0.0 func startDownload() { print("Function called!") // 重置进度状态 downloadProgress = 0.0 lastUpdatedProgress = 0.0 showProgressView = true let queue = DispatchQueue(label: "alamofire", qos: .utility) let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory) AF.download("https://speed.hetzner.de/10GB.bin", to: destination) .downloadProgress(queue: queue) { [weak self] progress in guard let self = self else { return } let currentFraction = progress.fractionCompleted // 仅当进度变化超过1%时才更新UI guard currentFraction - self.lastUpdatedProgress >= 0.01 else { return } DispatchQueue.main.async { self.lastUpdatedProgress = currentFraction self.downloadProgress = currentFraction } } .response { [weak self] response in print(response) // 下载完成后强制更新到100% DispatchQueue.main.async { self?.downloadProgress = 1.0 self?.showProgressView = false } } } }
方案2:用Combine的throttle操作符做时间限流
固定每100ms最多更新一次UI,适合对进度平滑度要求更高的场景:
import Combine import SwiftUI import Alamofire class ViewModel: ObservableObject { @Published var currentDownload: DownloadRequest? = nil @Published var downloadProgress: Double = 0.0 @Published var showProgressView: Bool = false // 用Subject接收原始进度回调 private let progressSubject = PassthroughSubject<Double, Never>() // 保存订阅 private var cancellables = Set<AnyCancellable>() init() { // 绑定进度流:100ms内最多输出一次更新,强制在主线程处理 progressSubject .throttle(for: .milliseconds(100), scheduler: DispatchQueue.main, latest: true) .assign(to: &$downloadProgress) } func startDownload() { print("Function called!") // 重置进度状态 downloadProgress = 0.0 showProgressView = true let queue = DispatchQueue(label: "alamofire", qos: .utility) let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory) AF.download("https://speed.hetzner.de/10GB.bin", to: destination) .downloadProgress(queue: queue) { [weak self] progress in // 只把进度发送到Subject,不直接更新UI self?.progressSubject.send(progress.fractionCompleted) } .response { [weak self] response in print(response) DispatchQueue.main.async { self?.downloadProgress = 1.0 self?.showProgressView = false } } } }
额外优化建议
- 移除进度回调中的
print日志打印操作,高频打印也会占用不必要的性能 - 如果不需要精确到1%的进度展示,可以把阈值调到0.02/0.05,或者把throttle的时间间隔调到200ms,进一步降低主线程压力
内容的提问来源于stack exchange,提问作者code24
相关产品推荐
相关产品推荐

