在我的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()
方法的调用会产生以下编译器错误:
表达式为"async",但未标记为"await">
这对我来说没有意义,因为初始化器和方法不是异步的。此外,我在其他地方使用相同的模式,没有问题。例如,如果我将ViewControllerBase
转换为一个类,它编译得很好:
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编译器中的错误?
这对我来说没有意义,因为初始化器和方法不是异步的。
是的,但在另一种情况下,您必须说await
,并且目标方法被视为async
:即,当存在跨参与者上下文切换时。
这就是我的意思。
与ItemDetails/Base示例相比,UIKit的特殊之处在于,UIKit接口对象/方法被标记为@MainActor
,要求在主要参与者(实际上是指主线程(上运行。
但是在您的协议扩展代码中,没有任何东西需要在任何特定的参与者上运行。因此,当您调用ItemDetailsViewController初始值设定项或present
方法时,编译器会对自己说:
"嗯,当这个代码(showItem
(运行时,我们可能不是主要参与者。因此,这些对ItemDetailsViewController方法的调用可能需要上下文开关[从一个参与者更改为另一个参与者]。(">
当您需要将被调用的方法视为async
并称为await
时,上下文切换正是;因此编译器强制执行该要求。
使该要求在这里消失的简单方法是将您的方法也标记为@MainActor
。这样,编译器就知道当运行以下代码时,将不会出现上下文切换:
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 {
}
}