You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 14:03:21