为何无法在Swift类中初始化AnyPublisher属性?
我希望初始化类中的所有属性,让isEmptyPublisher在初始化完成后立即生效,而不是首次访问时才工作。为什么下面的写法无法初始化AnyPublisher<Bool, Never>属性?
class MyClass { private let name: String @Published private(set) var friends = [String]() let isEmptyPublisher: AnyPublisher<Bool, Never> init(name: String) { self.name = name self.isEmptyPublisher = $friends.map(\.isEmpty).eraseToAnyPublisher() } }
编译器抛出以下错误:
'self' used in property access '$friends' before all stored properties are initialized
'self.isEmptyPublisher' not initialized
我只能改用lazy属性的写法,但这不符合我“初始化后立即生效”的需求:
class MyClass { private let name: String @Published private(set) var friends = [String]() private(set) lazy var isEmptyPublisher: AnyPublisher<Bool, Never> = { return $friends.map(\.isEmpty).eraseToAnyPublisher() }() init(name: String) { self.name = name } }
Swift初始化有两个核心规则:
- 类的所有存储属性必须在初始化流程结束前完成赋值,保证
self处于完全可用状态。 - 在所有存储属性未初始化完成前,不能访问
self(包括通过属性包装器的投影值,比如$friends)。
你遇到的问题根源在$friends:@Published是属性包装器,$friends作为它的投影发布者,本质需要访问self来获取包装器实例。但在第一个初始化代码中,赋值isEmptyPublisher时,self还没完成所有存储属性的初始化(isEmptyPublisher本身还在赋值过程中),所以编译器判定你提前使用了self,抛出错误。
lazy属性之所以能运行,是因为它的初始化逻辑首次访问时才执行,此时self已经完全初始化,能安全访问$friends。但这种写法的问题是,isEmptyPublisher要等到第一次被调用时才会创建并开始监听friends的变化,没法满足你“初始化后立即生效”的需求。
如果你想让isEmptyPublisher在初始化完成后立即生效,同时满足属性初始化的要求,可以用以下两种方式:
方案1:结合defer和隐式解包可选类型
class MyClass { private let name: String @Published private(set) var friends = [String]() private(set) var isEmptyPublisher: AnyPublisher<Bool, Never>! init(name: String) { self.name = name // defer块会在所有存储属性初始化完成后执行 defer { self.isEmptyPublisher = $friends.map(\.isEmpty).eraseToAnyPublisher() } } }
defer的作用是延迟执行代码块,直到当前初始化流程结束(所有存储属性都已赋值)。此时self完全可用,访问$friends不会触发错误,isEmptyPublisher也会在初始化完成后立即被赋值并开始工作。
方案2:保持let类型,合并初始值与变化发布者
如果你希望isEmptyPublisher是不可变的let类型,可以先创建包含初始值的发布者,再合并后续变化:
class MyClass { private let name: String @Published private(set) var friends = [String]() let isEmptyPublisher: AnyPublisher<Bool, Never> init(name: String) { self.name = name // 先获取初始的isEmpty值,创建立即发送的发布者 let initialValue = friends.isEmpty let initialPublisher = Just(initialValue) // 用Deferred延迟获取$friends,确保self初始化完成 let changesPublisher = Deferred { [weak self] in self?.$friends.map(\.isEmpty) ?? Empty().eraseToAnyPublisher() } // 合并初始值和后续变化 self.isEmptyPublisher = initialPublisher.merge(with: changesPublisher).eraseToAnyPublisher() } }
Deferred会延迟执行闭包直到订阅发生,此时self已经初始化完成,能安全访问$friends。isEmptyPublisher在初始化时就完成赋值,订阅后会先收到初始值,再监听后续的friends变化。
内容的提问来源于stack exchange,提问作者user10711707

