Swift严格并发检查下,如何用@MainActor属性作为默认值初始化类?
问题背景
原有代码将UIApplication.shared作为初始化默认参数,开启Strict Concurrency Checking的Complete选项后触发错误:
Main actor-isolated class property 'shared' can not be referenced from a non-isolated context
原因是UIApplication被标记为@MainActor,其所有属性(包括shared)必须在主线程隔离的上下文中访问:
@available(iOS 2.0, *) @MainActor open class UIApplication : UIResponder { open class var shared: UIApplication { get } // ... }
已有的两种方案
方案1:可选属性+@MainActor初始化器
将属性改为可选类型,在@MainActor修饰的初始化器中赋值,但会丢失依赖注入能力,降低可测试性,且使用时需处理可选值:final class MyClass { private var application: UIApplication? @MainActor init() { self.application = UIApplication.shared } }方案2:调用者负责MainActor上下文
移除默认参数,要求调用者在@MainActor上下文初始化类,虽然可行但需要重构所有调用处:final class MyClass { private var application: UIApplication init(application: UIApplication) { self.application = application } // 示例1:在@MainActor方法中初始化 @MainActor func option1ToInit() { _ = MyClass(application: UIApplication.shared) } // 示例2:通过Task切换到MainActor func option2ToInit() { Task { _ = await MyClass(application: UIApplication.shared) } } }
其他可行方案
方案3:用@autoclosure+@MainActor包装默认参数
通过@autoclosure将默认参数包装为闭包,并限定闭包在@MainActor上下文执行,既保留依赖注入能力,又解决并发隔离问题:
final class MyClass { private var application: UIApplication init(application: @autoclosure @MainActor () -> UIApplication = UIApplication.shared) { self.application = application() } }
说明:当调用MyClass()不传参数时,@autoclosure会自动将UIApplication.shared包装成闭包,且@MainActor确保闭包在主线程上下文执行;如果需要测试或自定义注入,依然可以手动传入UIApplication实例,完全不影响依赖注入的灵活性。
方案4:将类标记为@MainActor
如果MyClass本身就是和UI相关的类,直接将整个类标记为@MainActor,这样所有初始化器和类方法都会自动在主线程上下文执行,访问UIApplication.shared就不会有隔离问题:
@MainActor final class MyClass { private var application: UIApplication init(application: UIApplication = UIApplication.shared) { self.application = application } }
说明:此方案适合类的所有逻辑都需要在主线程执行的场景,如果有部分方法不需要主线程,可以单独标记为nonisolated来规避。
内容的提问来源于stack exchange,提问作者πter

