Async/await 协议扩展不支持?

Async/await not supported in protocol extension?

在我的 iOS 应用程序中,我有一个类似于以下的协议扩展:

protocol ViewControllerBase: UIViewController {
}

extension ViewControllerBase {
    func showItem(itemID: Int) {
        Task {
            await loadItemDetails(itemID)

            let itemDetailsViewController = ItemDetailsViewController(style: .grouped)

            present(itemDetailsViewController, animated: true)
        }
    }

    func loadItemDetails(_ itemID: Int) async {
    }
}

我正在使用协议扩展,以便所有实现 ViewControllerBase 的视图控制器都将继承 showItem() 方法。 ItemDetailsViewController定义如下:

class ItemDetailsViewController: UITableViewController {
}

loadItemDetails() 的调用编译正常,但对 ItemDetailsViewController 初始化器和 present() 方法的调用产生以下编译器错误:

Expression is 'async' but is not marked with 'await'

这对我来说没有意义,因为初始化程序和方法不是异步的。此外,我在其他地方使用相同的模式没有问题。例如,如果我将 ViewControllerBase 转换为 class,它编译得很好:

class ViewControllerBase: UIViewController {
    func showItem(itemID: Int) {
        Task {
            await loadItemDetails(itemID)

            let itemDetailsViewController = ItemDetailsViewController(style: .grouped)

            present(itemDetailsViewController, animated: true)
        }
    }

    func loadItemDetails(_ itemID: Int) async {
    }
}

这似乎与 UIKit 具体相关,因为这段代码也可以正常编译:

class ItemDetails {
}

protocol Base {
}

extension Base {
    func showItem(itemID: Int) {
        Task {
            await loadItemDetails(itemID)

            let itemDetails = ItemDetails()

            present(itemDetails)
        }
    }

    func loadItemDetails(_ itemID: Int) async {
    }

    func present(_ itemDetails: ItemDetails) {
    }
}

是否存在协议扩展中不允许此代码的原因,或者这可能是 Swift 编译器中的错误?

This doesn't make sense to me, since the initializer and method are not asynchronous.

是的,但是还有一种情况是你必须说await而目标方法被视为async:即当有一个cross-actor上下文切换时。

这就是我的意思。

与您的 ItemDetails / Base 示例相反,UIKit 的特别之处在于 UIKit 接口对象/方法被标记为 @MainActor,要求主要参与者 运行(意思是,在效果,主线程)。

但是在您的协议扩展代码中,没有任何东西需要 运行 任何特定的参与者。因此,当您调用 ItemDetailsViewController 初始化程序或 present 方法时,编译器会对自己说:

“嗯,当这段代码 (showItem) 运行 时,我们可能不是主角。所以这些对 ItemDetailsViewController 方法的调用可能需要 上下文切换 [从一个演员变成另一个演员].)"

当您需要将调用的方法视为async并说await时,上下文切换是恰好;因此编译器会强制执行该要求。

消除该要求的简单方法是将您的 方法也标记为@MainActor。这样,编译器知道当此代码 运行s:

时会有 no 上下文切换
extension ViewControllerBase {
    @MainActor func showItem(itemID: Int) {
        Task {
            await loadItemDetails(itemID)
            let itemDetailsViewController = ItemDetailsViewController(style: .grouped)
            present(itemDetailsViewController, animated: true)
        }
    }
    func loadItemDetails(_ itemID: Int) async {
    }
}