Swift:访问刚设置的属性时触发EXC_BAD_ACCESS(code=2)崩溃排查
根因分析
1. ViewModel初始化阶段的生命周期不稳定
你的BaseModuleViewModel继承自ObservableObject(推测BaseObservableViewModel实现了该协议),在init中刚完成module赋值就同步调用fetchData()。此时ViewModel实例可能还未被SwiftUI的视图层级强引用持有,处于临时对象状态。如果module包含引用类型子属性(比如meta是class),可能因实例未被稳定持有导致子属性提前释放,触发EXC_BAD_ACCESS。
2. 嵌套模块的时序与内存竞态
嵌套层级较深的页面,JSON解码、ViewModel创建是递归同步执行的,主线程会堆积大量初始化操作。这种场景下,部分子模块的模型实例可能还未完成全量初始化(比如meta的解码收尾操作未完成),就被fetchData()提前访问,引发内存访问冲突。
3. 临时缓解方案的本质
用DispatchQueue.main.async包装fetchData()能生效,是因为将数据加载延迟到了当前runloop周期结束后,此时ViewModel已被视图强引用,模型实例的内存状态趋于稳定,但这只是规避了问题,没有解决根源。
解决方案
方案1:延迟数据加载到ViewModel被稳定持有后
移除init中的fetchData()调用,改用SwiftUI的.task修饰符(推荐)或onAppear触发数据加载,确保ViewModel已被视图层级完全持有:
// 修改BaseModuleViewModel class BaseModuleViewModel<T: Module>: BaseObservableViewModel { let module: T init(module: T) { self.module = module super.init() // 移除init内的fetchData()调用 } func loadData() { fetchData() } private func fetchData() { guard let initAction = module.meta?.initAction else { return } // 执行你的业务逻辑 } } // 在模块视图中触发加载 struct GenericModuleView<VM: BaseModuleViewModelProtocol>: View { @StateObject var viewModel: VM var body: some View { // 你的视图内容 .task { viewModel.loadData() } } }
方案2:检查模型解码的引用类型持有逻辑
如果module.meta是class类型,确保解码后该实例被module强引用(避免用weak/unowned修饰),防止其提前释放:
// 示例Module结构体,确保meta为强引用 struct PageModule: Module { let meta: ModuleMeta? // ModuleMeta是class类型,用let强持有 // 其他模块属性 } class ModuleMeta: Decodable { let initAction: String? // 解码逻辑 }
方案3:优化嵌套模块的创建流程
对于深嵌套页面,将子模块ViewModel的创建改为懒加载,仅当子视图即将显示时才初始化并加载数据,避免主线程同步递归的内存压力:
// 用Lazy包装ViewModel的创建 class ModuleViewModelProvider { func createLazyViewModel(for module: Module) -> Lazy<BaseObservableViewModel> { Lazy { self.createViewModel(for: module) } } private func createViewModel(for module: Module) -> BaseObservableViewModel { // 原有ViewModel创建逻辑 } } // 在容器视图中按需初始化子模块ViewModel struct ContainerModuleView: View { let lazyChildViewModels: [Lazy<BaseObservableViewModel>] var body: some View { ForEach(lazyChildViewModels, id: \.hashValue) { lazyVM in ChildModuleView(viewModel: lazyVM.wrappedValue) } } }
方案4:排查fetchData()中的闭包引用
如果fetchData()内有异步闭包,确保使用[weak self]避免循环引用,同时防止因self提前释放导致的间接内存访问问题:
private func fetchData() { guard let initAction = module.meta?.initAction else { return } DispatchQueue.global().async { [weak self] in // 异步业务逻辑 DispatchQueue.main.async { self?.handleInitAction(initAction) } } }
内容的提问来源于stack exchange,提问作者Tony Dakhoul

