问题描述
在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()
        }
    }
}
											
				    