如何在Swift中异步加载多个数据?

问题描述 投票:0回答:1

我正在使用 SwiftUI 制作一个 iOS 应用程序。该应用程序将在视图中显示大量图像。图像以分层结构存储在云端。我正在尝试一种异步加载每个图像的方法。我想要实现的目标包括:

  1. 每张图片都可以异步独立加载。
  2. 图片的数量和地址是保存在云端的元数据的一部分,需要先从云端加载,然后再加载图片数据。
  3. 图像加载不应阻塞主线程中的 UI,即用户在图像加载时仍然可以滑动或按下按钮。
  4. 加载图像时,其占位符会显示进度视图。

我尝试过以下(简化的)代码:

@MainActor // <-- Need this to avoid the warning
class Metadata: ObservableObject {
    @Published var isLoaded = false // <-- warning if w/o @MainActor: Publishing changes from background threads is not allowed

    var images: [ImageData] = []
    
    func load() async {
        // Load metadata from cloud...
        images.append(ImageData())
        images.append(ImageData())
        images.append(ImageData())
        
        sleep(1) // To simulate loading

        isLoaded = true
    }
}

@MainActor // <-- Need this to avoid the warning
class ImageData: ObservableObject {
    @Published var isLoaded = false // <-- warning if w/o @MainActor: Publishing changes from background threads is not allowed
    
    var data: String = "N/A" // image data or payload

    func load() async {
        // Load image data from cloud...
        data = "Image data is loaded."
        
        let us = Int.random(in: 1000000..<3000000) // 1 to 3 sec
        usleep(UInt32(us))  // To simulate loading
        
        isLoaded = true
    }
}

和观点:

struct ContentView: View {
    @StateObject var metadata: Metadata = Metadata()
    
    var body: some View {
        Group {
            if metadata.isLoaded {
                VStack {
                    ForEach(0..<metadata.images.count, id: \.self) { i in
                        ImageView(image: metadata.images[i])
                    }
                }
            } else {
                ProgressView()
            }
        }
        .task {
            await metadata.load()
        }
    }
}

struct ImageView: View {
    @ObservedObject var image: ImageData
    
    var body: some View {
        Group {
            if image.isLoaded {
                Text(image.data)
            } else {
                ProgressView()
            }
        }
        .task {
            await image.load()
        }
    }
}

我的问题:

  1. 不添加
    @MainActor
    允许视图异步加载每个图像。但是,我收到了警告消息
    Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates. 
    ,有时并非所有加载都完成。
  2. 添加
    @MainActor
    修复了警告,但所有加载都在主线程上运行,因此需要很长时间才能完成所有图像的加载。此外,用户的交互将被阻止。
  3. 我也考虑过使用
    AsyncImage
    ,但也想找到一个适用于所有类型数据的通用解决方案,而不仅限于图像。

有没有好的方法来处理这种情况?谢谢。

ios swift asynchronous swiftui
1个回答
0
投票

根据@workingdogsupportukraine的建议,以下更改解决了我的问题

class Metadata: ObservableObject {
    @Published var isLoaded = false

    var images: [ImageData] = []
    
    func load() async {
        // Loading metadata from cloud...
        images.append(ImageData())
        images.append(ImageData())
        images.append(ImageData())
        
        do {
            try await Task.sleep(nanoseconds: 1 * 1_000_000_000)
        } catch {}

         DispatchQueue.main.async {
             self.isLoaded = true
         }
        // or
        // Task { @MainActor in
        //     self.isLoaded = true
        // }
    }
}

class ImageData: ObservableObject {
    @Published var isLoaded = false
    
    var data: String = "N/A"

    func load() async {
        // Loading image data from cloud...
        data = "Image data is loaded."
        
        do {
            let sec = Int.random(in: 1..<5)
            try await Task.sleep(nanoseconds: UInt64(sec) * 1_000_000_000)
        } catch {}

        DispatchQueue.main.async {
            self.isLoaded = true
        }
        // or
        // Task { @MainActor in
        //     self.isLoaded = true
        // }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.