0

我在尝试通过将闭包传递给MainActor.run.

为了说明这个问题,考虑一个计算文件夹及其子目录中文件数量的函数。它在Task.detached闭包中运行,因为我不希望它阻塞主线程。每 10,000 个文件更新一个Published属性fileCount,通过将闭包传递给MainThread.run.

但是,用户界面无法更新,甚至给了我一个旋转的沙滩球。阻止这种情况的唯一方法是await Task.sleep(1_000_000_000)在调用之前插入MainThread.run. 这是代码:

final class NewFileCounter: ObservableObject {
    @Published var fileCount = 0

    func findImagesInFolder(_ folderURL: URL) {
        let fileManager = FileManager.default

        Task.detached {
            var foundFileCount = 0
            let options = FileManager.DirectoryEnumerationOptions(arrayLiteral: [.skipsHiddenFiles, .skipsPackageDescendants])
            
            if let enumerator = fileManager.enumerator(at: folderURL, includingPropertiesForKeys: [], options: options) {
                while let _ = enumerator.nextObject() as? URL {
                    foundFileCount += 1
                    if foundFileCount % 10_000 == 0 {
                        let fileCount = foundFileCount
                        await Task.sleep(1_000_000_000) // <-- Only works with this in...comment out to see failure
                        await MainActor.run { self.fileCount = fileCount }
                    }
                }
                let fileCount = foundFileCount
                await MainActor.run { self.fileCount = fileCount }
            }
        }
    }
}

如果我恢复到实现此目的的旧方法,该代码将起作用:

final class OldFileCounter: ObservableObject {
    @Published var fileCount = 0
    
    func findImagesInFolder(_ folderURL: URL) {
        let fileManager = FileManager.default

        DispatchQueue.global(qos: .userInitiated).async {           
            let options = FileManager.DirectoryEnumerationOptions(arrayLiteral: [.skipsHiddenFiles, .skipsPackageDescendants])
            var foundFileCount = 0
            
            if let enumerator = fileManager.enumerator(at: folderURL, includingPropertiesForKeys: [], options: options) {
                while let _ = enumerator.nextObject() as? URL {
                    foundFileCount += 1
                    if foundFileCount % 10_000 == 0 {
                        let fileCount = foundFileCount
                        DispatchQueue.main.async { self.fileCount = fileCount }
                    }
                }
                let fileCount = foundFileCount
                DispatchQueue.main.async { self.fileCount = fileCount }
            }
        }
    }
}

我究竟做错了什么?

顺便说一句 - 如果你想尝试这段代码,这里有一个测试工具。请务必选择一个包含大量文件的文件夹及其子文件夹。

import SwiftUI

@main
struct TestFileCounterApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    @State private var showPickerOld = false
    @StateObject private var fileListerOld = OldFileCounter()
    @State private var showPickerNew = false
    @StateObject private var fileListerNew = NewFileCounter()
    
    var body: some View {
        VStack {
            Button("Select folder to count files using DispatchQueue...") { showPickerOld = true }
            Text("\(fileListerOld.fileCount)").foregroundColor(.green)
                .fileImporter(isPresented: $showPickerOld, allowedContentTypes: [.folder], onCompletion: processOldSelectedURL )
            Divider()
            Button("Select folder to count files using Swift 5.5 concurrency...") { showPickerNew = true }
            Text("\(fileListerNew.fileCount)").foregroundColor(.green)
                .fileImporter(isPresented: $showPickerNew, allowedContentTypes: [.folder], onCompletion: processNewSelectedURL )
        }
        .frame(width: 400, height: 130)
    }
    
    private func processOldSelectedURL(_ result: Result<URL, Error>) {
        switch result {
            case .success(let url): fileListerOld.findImagesInFolder(url)
            case .failure: return
        }
    }
    
    private func processNewSelectedURL(_ result: Result<URL, Error>) {
        switch result {
            case .success(let url): fileListerNew.findImagesInFolder(url)
            case .failure: return
        }
    }
}
4

1 回答 1

0

Apple 已解决此问题 - MacOS 12.1 beta 3、Xcode 13.2 Beta 2。

于 2021-11-18T12:02:14.937 回答