在SwiftUI的watchOS应用中向数组追加元素时崩溃的问题排查
我帮你分析了代码里的问题,主要是ObservableObject的生命周期管理错误和EnvironmentObject注入方式不对导致的崩溃,下面是具体的问题点和修复方案:
问题根源
用
@State存储ObservableObject是错误的@State是SwiftUI专门为值类型(比如String、Int、自定义结构体)设计的属性包装器,用来管理View内部的状态。而SubjectDataEnv是引用类型(class),用@State存储会导致它的生命周期异常,可能在View刷新时被意外重建或者释放,当你尝试向数组追加元素时,就会因为实例状态异常引发崩溃。WKHostingController没有正确注入根环境对象
你的HostingController只是返回了一个全新的ContentView(),没有把SubjectDataEnv实例注入到根View的环境中。而ContentView自己创建的subjectData实例,在传递给子View时,可能因为环境对象的层级问题,导致子View拿到的实例和预期不一致,更新数组时触发了无效的UI更新逻辑。
修复步骤
第一步:修改WKHostingController,管理根ObservableObject实例
把SubjectDataEnv的创建移到HostingController中,这里是整个watchOS应用的根控制器,能保证实例的生命周期和应用一致:
class HostingController: WKHostingController<ContentView> { // 在这里创建ObservableObject实例,作为全局环境对象的来源 let subjectData = SubjectDataEnv() override var body: ContentView { // 将实例注入到ContentView的环境中 ContentView().environmentObject(subjectData) } }
第二步:修正ContentView的环境对象接收方式
移除ContentView中错误的@State声明,改为通过@EnvironmentObject接收来自根控制器的实例:
struct ContentView: View { @EnvironmentObject var subjectData: SubjectDataEnv var body: some View { // 子View会自动继承环境中的subjectData,不需要手动传递 SubjectView() } }
第三步:简化子View的环境对象传递
原来的SubjectView和AddSubjectView已经用@EnvironmentObject接收实例,现在不需要在NavigationLink里手动传递environmentObject了,因为环境会自动向下传递:
struct SubjectView: View { @EnvironmentObject var subjectData: SubjectDataEnv var body: some View { List { ForEach(subjectData.store) { subject in NavigationLink(destination: DurationView()) { Text(subject.name) } } NavigationLink(destination: AddSubjectView()) { Text("+") } } } }
第四步:确认AddSubjectView的逻辑
你的追加代码本身没问题,修复上面的环境对象问题后,这部分代码就能正常工作了:
Button(action: { self.subjectData.store.append(Subject(name: self.addedSubject, isFavorite: false)) }) { Text("Done") }
为什么这样能解决崩溃?
通过在WKHostingController中创建并注入SubjectDataEnv实例,我们保证了整个应用中只有一个全局的数据源实例,所有子View都共享这个实例。同时,使用@EnvironmentObject正确接收环境中的实例,避免了@State管理引用类型带来的生命周期异常,数组追加时的UI更新也能正常触发,不会再出现崩溃。
内容的提问来源于stack exchange,提问作者Sid

