我试图了解如何在异步上下文中使用没有 Sendable 的 Apple 类,而不会收到这在 Swift 6 中不起作用的警告。
选择的武器是
NSExtensionContext
,我需要获取已传递到共享扩展中的 URL。
这是我的原始代码,它只是从扩展上下文中获取 URL。启用新的并发检查后,它会发出警告:
主要参与者隔离的属性“url”不能从 Sendable 闭包中发生变化;这是 Swift 6 中的错误
class ShareViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
fetchURL()
}
private func fetchURL() {
guard let extensionContext = self.extensionContext,
let item = extensionContext.inputItems.first as? NSExtensionItem,
let attachments = item.attachments else { return }
for attachment in attachments {
if attachment.hasItemConformingToTypeIdentifier("public.url") {
attachment.loadItem(forTypeIdentifier: "public.url") { url, error in
guard let url else { return }
self.url = url as? URL <-- WARNING! Main actor-isolated property 'url' can not be mutated from a Sendable closure; this is an error in Swift 6
}
}
}
}
}
我知道该函数是在
MainActor
上调用的,但 extensionContext
可以用于任何演员,这就是投诉的原因。
首先,我是否可以将 url 属性标记为
Sendable
,以便任何参与者都可以对其进行修改?
尝试不同的东西,我修改了它以使用最新的
async/await
版本的extensionContect
。
override func viewDidLoad() {
super.viewDidLoad()
Task {
await fetchURL()
}
}
private func fetchURL() async {
guard let extensionContext = self.extensionContext,
let item = extensionContext.inputItems.first as? NSExtensionItem,
let attachments = item.attachments else { return }
for attachment in attachments {
if let url = try? await attachment.loadItem(forTypeIdentifier: "public.url") as? URL { <-- WARNINGS!
self.url = url
}
}
}
这实际上在同一行给了我 4 个警告!
对非隔离函数隐式异步调用返回的不可发送类型“任何 NSSecureCoding”不能跨越参与者边界
传递不可发送类型的参数“[AnyHashable:Any]?”在主要参与者隔离的上下文之外可能会引入数据竞争
传递不可发送类型的参数“[AnyHashable:Any]?”在主要参与者隔离的上下文之外可能会引入数据竞争
在主要参与者隔离的上下文之外传递不可发送类型“NSItemProvider”的参数可能会引入数据竞争
让我们尝试分离任务,以便它在新的 Actor 上运行:
private func fetchURL() async {
guard let extensionContext = self.extensionContext,
let item = extensionContext.inputItems.first as? NSExtensionItem,
let attachments = item.attachments else { return }
Task.detached {
for attachment in attachments { <-- WARNING! Capture of 'attachments' with non-sendable type '[NSItemProvider]' in a `@Sendable` closure
let attachment = attachment
if let url = try? await attachment.loadItem(forTypeIdentifier: "public.url") as? URL {
await MainActor.run { [weak self] in
self?.url = url
}
}
}
}
}
仅此 1 个警告:
在
闭包中捕获不可发送类型“[NSItemProvider]”的“附件”@Sendable
最后的尝试,让我们把一切都放在超然的演员身上。这需要使用
extensionContext
:异步访问
await
private func fetchURL() async {
Task.detached { [weak self] in
guard let extensionContext = await self?.extensionContext, <-- WARNING! Non-sendable type 'NSExtensionContext?' in implicitly asynchronous access to main actor-isolated property 'extensionContext' cannot cross actor boundary
let item = extensionContext.inputItems.first as? NSExtensionItem,
let attachments = item.attachments else { return }
for attachment in attachments {
let attachment = attachment
if let url = try? await attachment.loadItem(forTypeIdentifier: "public.url") as? URL {
await MainActor.run { [weak self] in
self?.url = url
}
}
}
}
}
我们收到错误:
不可发送类型“NSExtensionContext?”在隐式异步访问主参与者隔离属性“extensionContext”时不能跨越参与者边界
我知道清除所有警告的一种方法:
extension NSExtensionContext: @unchecked Sendable {}
我遇到的问题是使用
@unchecked
似乎就像告诉编译器忽略后果。
在
extensionContext
上运行的 UIViewController
中使用此 @MainActor
的正确方法是什么?
您最终想要使用
async
版本的 loadItem
的第二个示例。 SE-0414(基于区域的隔离)应修复发货时的警告。
与此同时,您的第一个示例根本不正确。这是一个竞争条件,因为
loadItem
不承诺在主要参与者上调用其闭包:
该块可以在后台线程上执行。
Swift 正确地警告您有关此错误的信息。您可以像往常一样修复它,将 actor 的更新移动到 actor 的上下文中:
Task { @MainActor in
self.url = url as? URL
}
这会给您留下
NSSecureCoding
警告。这是因为 Foundation 和 UIKit 尚未完全注释。在此之前,您应该在需要时将其导入为 @preconcurrency
。 (如果您添加不必要的注释,编译器会警告您。)
@preconcurrency import UIKit
通过这两项更改,我在 Xcode 15.3 中的“完整”并发下没有看到任何警告。