如何在 swift 中处理不可发送类型?

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

我试图了解如何在异步上下文中使用没有 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 个警告:

@Sendable
闭包中捕获不可发送类型“[NSItemProvider]”的“附件”

最后的尝试,让我们把一切都放在超然的演员身上。这需要使用

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
的正确方法是什么?

ios swift asynchronous async-await sendable
1个回答
1
投票

您最终想要使用

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 中的“完整”并发下没有看到任何警告。

© www.soinside.com 2019 - 2024. All rights reserved.