为何@StateObject可配合合成初始化器使用,手写初始化器却不行?
当我们在SwiftUI中使用@StateObject修饰视图属性时,用自动合成的初始化器可以正常编译,但手写初始化器直接给viewModel赋值时,会抛出“Cannot assign to property: 'viewModel' is a get-only property”错误,两段代码对比如下:
正常编译的代码:
struct MyView: View { @StateObject var viewModel: MyViewModel var body: some View { TextField("", text: $viewModel.myProperty) } }
报错的代码:
struct MyView: View { @StateObject var viewModel: MyViewModel init() { viewModel = MyViewModel() // 传入参数也会触发相同错误 } var body: some View { TextField("", text: $viewModel.myProperty) } }
错误原因
@StateObject是SwiftUI提供的属性包装器,核心作用是让SwiftUI接管对象的生命周期——在视图首次创建时初始化对象,并在视图被销毁前维持对象存活。
我们直接编写的viewModel变量,是属性包装器对外暴露的投影属性,这个属性是只读的:因为赋值逻辑已经被SwiftUI完全接管,不允许开发者手动修改。而Swift自动生成的合成初始化器,会绕过这个只读的投影属性,直接初始化属性包装器的底层存储部分,因此不会触发错误。
当我们手写初始化器直接给viewModel赋值时,本质是在尝试修改这个只读的投影属性,自然会触发编译错误。
解决办法
如果确实需要手写初始化器(比如需要传入自定义参数初始化ViewModel),我们需要直接操作@StateObject的底层存储属性——也就是以下划线开头的_viewModel,通过StateObject的初始化方法传入wrappedValue完成初始化:
struct MyView: View { @StateObject var viewModel: MyViewModel init(someInitialValue: String) { // 直接操作底层存储属性,传入初始化后的ViewModel实例 _viewModel = StateObject(wrappedValue: MyViewModel(myProperty: someInitialValue)) } var body: some View { TextField("", text: $viewModel.myProperty) } }
总结
@StateObject的设计初衷是让SwiftUI管理对象的生命周期,除非必要,尽量优先使用自动合成的初始化器。如果必须手写初始化器,一定要操作底层的_xxx存储属性,而非直接给投影属性赋值。
内容的提问来源于stack exchange,提问作者Joakim Sjöstedt

