使用Swift Concurrency将[NSItemProvider]内容的顺序访问改为并行
解决Action Extension中Swift Concurrency并行处理NSItemProvider的问题
一、消除TaskGroup捕获NSItemProvider的Sendable警告
NSItemProvider本身未实现Sendable协议,直接在TaskGroup子任务中捕获会触发编译器警告。结合Action Extension场景,传入的NSItemProvider均为一次性只读对象,不会在多线程中被修改,因此可安全使用@unchecked Sendable消除警告,同时保留并行处理逻辑。
修改后的loadImages方法示例:
import UniformTypeIdentifiers import UIKit class ImageImporter { func loadImages(from providers: [NSItemProvider]) async throws -> [UIImage] { return try await withThrowingTaskGroup(of: UIImage?.self) { group in for provider in providers { // 用@unchecked Sendable标记,确保跨线程传递的安全性 group.addTask { [unchecked provider] in // 处理本地图片 if provider.hasItemConformingToTypeIdentifier(UTType.image.identifier) { return try await provider.loadObject(ofClass: UIImage.self) as? UIImage } // 处理远程图片URL else if provider.hasItemConformingToTypeIdentifier(UTType.url.identifier) { guard let url = try await provider.loadObject(ofClass: URL.self) as? URL else { return nil } let (data, _) = try await URLSession.shared.data(from: url) return UIImage(data: data) } return nil } } // 收集所有有效图片 var resultImages: [UIImage] = [] for try await image in group { if let validImage = image { resultImages.append(validImage) } } return resultImages } } }
若不想使用@unchecked Sendable,也可提前将每个NSItemProvider的类型判断结果提取到本地变量后传入子任务,但这种方式会增加冗余代码,前者在Action Extension场景下更高效。
二、移除@MainActor后,安全更新主线程UI
UI操作必须在主线程执行,无论ImageImporter是否标记@MainActor,只要在获取图片数组后主动切换到主线程处理UI更新即可,常用两种方式:
方式1:用MainActor.run包装UI更新逻辑
在调用loadImages的代码中,拿到结果后直接切换主线程:
// 假设在Extension处理入口或视图控制器中 let imageImporter = ImageImporter() let inputProviders: [NSItemProvider] = // 从Extension上下文获取的内容 Task { do { let processedImages = try await imageImporter.loadImages(from: inputProviders) // 切换到主线程更新UI await MainActor.run { self.displayedImages = processedImages self.imageCollectionView.reloadData() } } catch { // 错误处理也切换到主线程 await MainActor.run { self.showErrorAlert(message: error.localizedDescription) } } }
方式2:给UI更新方法标记@MainActor
将UI更新方法单独标记为@MainActor,调用时自动切换线程:
// 在视图控制器/Extension处理类中 @MainActor private func updateImageDisplay(with images: [UIImage]) { self.displayedImages = images self.imageCollectionView.reloadData() } @MainActor private func showErrorAlert(message: String) { let alert = UIAlertController(title: "加载失败", message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "确定", style: .default)) present(alert, animated: true) } // 调用loadImages的代码 Task { do { let processedImages = try await imageImporter.loadImages(from: inputProviders) await updateImageDisplay(with: processedImages) } catch { await showErrorAlert(message: error.localizedDescription) } }
这种方式代码更整洁,UI相关逻辑的线程安全由@MainActor自动保证。
内容的提问来源于stack exchange,提问作者Joshua Sullivan
相关产品推荐
相关产品推荐

