我正在试图理解如何在没有Sendable的情况下在异步环境中使用Apple类,而不会收到这在Swift 6中不起作用的警告.

Select 的武器是NSExtensionContext,我需要它来获取已传递到共享扩展中的URL.

这是我的原始代码,它只是从扩展上下文中获取URL.启用新的并发判断后,它会发出警告:

不能从可发送闭包中变异主参与者隔离的属性‘url’;这是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! 不能从可发送闭包中变异主参与者隔离的属性‘url’;这是SWIFT 6中的错误
                }
            }
        }
    }
}

我知道这个功能是调用MainActor,但extensionContext可以使用在任何演员,这是投诉的原因.

首先,我是否可以将url属性标记为Sendable,以便可以从任何参与者修改它?

为了try 一些不同的东西,我对其进行了修改,以使用最新的async/awaitextensionContect.

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个警告!

隐式异步调用非隔离函数返回的不可发送类型""Any NSSecureCoding""不能跨越执行元边界

传递不可发送类型的参数"[AnyHasable:Any]?"在主要参与者之外-孤立的环境可能会引发数据竞赛

传递不可发送类型的参数"[AnyHasable:Any]?"在主要参与者之外-孤立的环境可能会引发数据竞赛

将不可发送类型"NSItemProvider"的参数传递到主执行元隔离上下文之外可能会导致数据竞争

让我们试着分离Task,以便它在一个新的参与者上运行:

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
                }
            }
        }
    }
}

这里只有一条警告:

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

最后一次,让我们把一切都放在超然的演员身上.这需要使用await异步访问extensionContext:

private func fetchURL() async {
    
    Task.detached { [weak self] in
        guard let extensionContext = await self?.extensionContext, <-- WARNING! 不可发送类型‘NSExtensionContext?’在隐式异步访问主执行元隔离属性"extsionContext"时,不能跨越执行元边界
              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?’在隐式异步访问主执行元隔离属性"extsionContext"时,不能跨越执行元边界

我知道一种清除所有警告的方法:

extension NSExtensionContext: @unchecked Sendable {}

我的问题是,使用@unchecked似乎就像是告诉编译器忽略其后果.

@MainActor上运行的UIViewController中使用这extensionContext的正确方式是什么?

推荐答案

您的第二个使用loadItemasync版本的示例最终将是您想要的.SE-0414(基于区域的隔离)应该会在发货时修复该警告.

同时,你的第一个例子是完全不正确的.这是一种竞争条件,因为loadItem:does not promise to call its closure on the main actor:

该块可以在后台线程上执行.

Swift 正确地警告了你这个漏洞.您可以像往常一样修复它,方法是将参与者的更新移动到参与者的上下文:

Task { @MainActor in
    self.url = url as? URL
}

这将给您留下NSSecureCoding的警告.这是因为Foundation和UIKit还没有完全注释.在此之前,您应该在需要的时候导入为@preconcurrency.(如果您添加了不必要的注释,编译器会发出警告.)

@preconcurrency import UIKit

有了这两个更改,我在Xcode15.3中的"完全"并发下看不到任何警告.

Ios相关问答推荐

SWIFT AVFoundation翻转相机功能不起作用

如果没有等待,SWIFT Async让任务抛出取消异常

objc[25442]:Swift类的扩展和类别不允许有+load方法

仅更改 AttributedString 的字符串(例如 UIButton 配置 attributeTitle)

比较 `某个协议` 实例时出现编译错误

SwiftUI 每秒从 Timer 更新单个 @State 属性,每秒也重绘整个视图

小部件链接有效,但不执行任何操作

SwiftUI DatePicker 格式在每个设备上都不一样,如何让它总是显示相同的 Select 版本?

SwiftUI - 使用切换switch 切换键盘类型(默认/数字)?

如何在不支持并发的自动关闭中修复'async'调用?

.overlay 和 body 是什么 swift 概念

如何用 Swift 圆化 UILabel 的边缘

如何使用 swift 添加速率按钮的链接?

以编程方式 Select UITextField 中的所有文本

Xcode / iOS模拟器:手动触发重大位置更改

如何删除 Swift 数组中的所有 nil 元素?

闭包不能隐式捕获变异的 self 参数

如何防止 iOS 设备上的显示屏变暗和关闭?

在 SKScene 中设置按钮

AFNetworking 发布请求