问题描述
在SwiftUI的入口文件中,我尝试在init()中使用Task调用方法:
@main
struct ImageSlimApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@StateObject var iapManager = IAPManager.shared
init() {
KeyboardMonitor.shared.startMonitoring()
Task {
await iapManager.loadProduct() // 加载产品信息
await iapManager.handleTransactions() // 加载内购交易更新
}
}
var body: some Scene {
// 空 Scene,窗口由 AppDelegate 管理
Settings {} // 占位,不弹出任何窗口
}
}
但是Task {} 发生报错:
Task {
await iapManager.loadProduct() // 报错,Escaping closure captures mutating 'self' parameter
await iapManager.handleTransactions()
}
经过查询了解到,在init()中使用Task{}时,因为这个Task闭包捕获了self:
await iapManager.loadProduct() // iapManager 是 @StateObject var iapManager = IAPManager.shared
self在init()初始化时,是正在构造中的可变结构实体,不能安全地被逃逸闭包捕获。
解决方案
将异步逻辑封装成静态函数或 @MainActor 函数,在 Task 中调用它,避免直接捕获 self:
@main
struct ImageSlimApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@StateObject var iapManager = IAPManager.shared
init() {
KeyboardMonitor.shared.startMonitoring()
Task {
await Self.startIAP() // 在Task中调用 @MainActor函数
}
}
var body: some Scene {
Settings {} // 空 Scene
}
@MainActor // 封装成 @MainActor 函数
static func startIAP() async {
let manager = IAPManager.shared
await manager.loadProduct()
await manager.handleTransactions()
}
}
这样就可以解决报错的问题。
总结
问题的主要原因在于,Swift结构体在init()构造期间,self是可变的。所以不能让self逃逸到异步闭包中。
即使是在Task中访问@StateObject对象,Swift也会任务捕获整个self,从而触发编译器错误。
如果是在mac中使用AppDelegate,也可以将初始化逻辑写进AppDelegate的applicationDidFinishLaunching方法中:
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
Task {
await IAPManager.shared.loadProduct()
await IAPManager.shared.handleTransactions()
}
}
}