@MainActor环境下传递[NSSortDescriptor]?至CloudKitUtility.fetch()的报错与解决
解决非Sendable类型跨Actor边界传递的CloudKit请求错误
报错信息
Non-sendable type '[NSSortDescriptor]?' exiting main actor-isolated context in call to non-isolated static method 'fetch(recordType:predicate:sortDescriptions:resultsLimit:)' cannot cross actor boundary
触发错误的代码
@MainActor func fetch() async { let predicate = NSPredicate(value: true) let sortDescriptors: [NSSortDescriptor] = [] print("fetch...") await MainActor.run { self.isLoading = true } do { let (items, cursor): ([Video], CKQueryOperation.Cursor?) = try await CloudKitUtility.fetch(recordType: Video.recordTypeName, predicate: predicate, sortDescriptions: sortDescriptors, resultsLimit: 100) // await MainActor.run { self.videos = items self.cursor = cursor self.isLoading = false print("fetch done, total = \(videos.count), has more items = \(self.hasMore)") // } } catch { // await MainActor.run { self.isLoading = false // } print(error.localizedDescription) } }
问题原因
NSSortDescriptor是NSObject子类,默认不遵循Sendable协议。被@MainActor修饰的方法处于主Actor隔离上下文,调用非隔离的CloudKitUtility.fetch时,传递的sortDescriptions参数无法安全跨Actor边界,因此编译器抛出错误。
解决方案
1. 让NSSortDescriptor遵循Sendable协议
NSSortDescriptor本身是线程安全的(Apple官方文档确认),可以通过扩展让它遵循Sendable:
extension NSSortDescriptor: @unchecked Sendable {}
使用@unchecked是因为Objective-C类型无法被编译器自动验证Sendable合规性,手动确认线程安全后添加该标记即可。此方案全局生效,能一次性解决所有同类问题。
2. 将参数传递逻辑移至非主Actor上下文
用Task.detached创建非隔离任务,在任务内部处理参数创建和请求调用,避免跨Actor传递非Sendable类型:
@MainActor func fetch() async { await MainActor.run { self.isLoading = true } do { // 在非隔离任务中处理参数和请求 let (items, cursor) = try await Task.detached { let predicate = NSPredicate(value: true) let sortDescriptors: [NSSortDescriptor] = [] return try await CloudKitUtility.fetch( recordType: Video.recordTypeName, predicate: predicate, sortDescriptions: sortDescriptors, resultsLimit: 100 ) as ([Video], CKQueryOperation.Cursor?) }.value // 更新UI必须回到主Actor await MainActor.run { self.videos = items self.cursor = cursor self.isLoading = false print("fetch done, total = \(videos.count), has more items = \(self.hasMore)") } } catch { await MainActor.run { self.isLoading = false } print(error.localizedDescription) } }
3. 标记CloudKitUtility.fetch为@MainActor
如果CloudKitUtility.fetch的实现依赖主Actor资源,或者希望它在主Actor上下文执行,直接给该方法添加@MainActor修饰:
extension CloudKitUtility { @MainActor static func fetch(recordType: String, predicate: NSPredicate, sortDescriptions: [NSSortDescriptor]?, resultsLimit: Int) async throws -> ([Video], CKQueryOperation.Cursor?) { // 原方法实现代码 } }
这样方法与调用方处于同一Actor上下文,参数传递不会触发跨边界错误。
内容的提问来源于stack exchange,提问作者foolbear
相关产品推荐
相关产品推荐

