UI 使用 AlamoFire downloadProgress 时锁定

UI locking up when using AlamoFire downloadProgress

我正在尝试创建下载进度条并在下载完成时同时显示提醒。

对于此任务,我将 AlamoFire 与 SwiftUI 结合使用,因为它使下载变得容易。 但是,当我使用带有 Published 变量的 ProgressView 跟踪进度时,整个 UI 锁定,我不知道如何修复它。

我尝试将 downloadProgress 添加到单独的 DispatchQueue,但我仍然必须从主线程更新 UI 否则 Xcode 会报错。

如何测试附加的示例代码:

如有任何帮助,我将不胜感激。

导入斯威夫特UI 进口 Alamofire

struct ContentView: View {
    @StateObject var viewModel: ViewModel = ViewModel()
    @State private var showAlert = false

    var body: some View {
        VStack {
            Button("Show Alert") {
                showAlert.toggle()
            }
            
            Button("Start download") {
                viewModel.startDownload()
            }
            
            if viewModel.showProgressView {
                ProgressView("Downloading…", value: viewModel.downloadProgress, total: 1.0)
                    .progressViewStyle(.linear)
            }
        }
        .alert(isPresented: $showAlert) {
            Alert(
                title: Text("Text"),
                dismissButton: .cancel()
            )
        }
    }
}

class ViewModel: ObservableObject {
    @Published var currentDownload: DownloadRequest? = nil
    @Published var downloadProgress: Double = 0.0
    @Published var showProgressView: Bool = false
    
    func startDownload() {
        print("Function called!")
        
        showProgressView.toggle()
        
        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) { progress in
                print(progress.fractionCompleted)
                
                DispatchQueue.main.async {
                    self.downloadProgress = progress.fractionCompleted
                }
            }
            .response { response in
                print(response)
            }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

这里的问题是您实际上是在用更新向 UI 线程发送垃圾邮件,因为 alamofire 经常调用提供给 downloadProgress 的闭包(查看控制台打印)。您需要稍微错开 AF 进度的更新,以便按下按钮关闭警报可以注册(在 Combine 中,这称为去抖动)。我在这里所做的是添加一个小的时间计数器,以便它仅每 1 秒更新一次进度。这些更新之间的时间使 UI 线程可以自由响应点击等

import SwiftUI
import Alamofire

struct ContentView: View {
    @StateObject var viewModel: ViewModel = ViewModel()
    @State private var showAlert = false

    var body: some View {
        VStack {
            Button("Show Alert") {
                showAlert.toggle()
            }
            
            Button("Start download") {
                viewModel.startDownload()
            }
            
            if viewModel.showProgressView {
                ProgressView("Downloading…", value: viewModel.downloadProgress, total: 1.0)
                    .progressViewStyle(.linear)
            }
        }
        .alert(isPresented: $showAlert) {
            Alert(
                title: Text("Text"),
                dismissButton: .cancel()
            )
        }
    }
}

class ViewModel: ObservableObject {
    @Published var currentDownload: DownloadRequest? = nil
    @Published var downloadProgress: Double = 0.0
    @Published var showProgressView: Bool = false
    
    func startDownload() {
        print("Function called!")
        
        showProgressView.toggle()
        
        let queue = DispatchQueue(label: "net", qos: .userInitiated)
        let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
        var last = Date()
        AF.download("https://speed.hetzner.de/10GB.bin", to: destination)
            .downloadProgress(queue:queue) { progress in
                print(progress.fractionCompleted)
                if Date().timeIntervalSince(last) > 1 {
                    last = Date()
                    DispatchQueue.main.async {
                        self.downloadProgress = progress.fractionCompleted
                    }
                }
            }
            .response { response in
//                print(response)
            }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}