Sheet消失后写入UserDefaults时触发SIGABRT崩溃问题排查
分析与解决方案
从你提供的崩溃堆栈和代码来看,这个崩溃的核心原因是SwiftUI在iOS 14.4版本中,视图生命周期与ObservableObject更新的冲突导致的已释放unowned引用访问。具体拆解如下:
崩溃根源定位
堆栈中的swift_abortRetainUnowned和swift_unknownObjectUnownedLoadStrong明确指向:代码尝试访问一个已经被销毁的unowned对象。结合调用链来看,问题发生在:
- Sheet的
onDisappear回调中执行changeCounter,修改了Preferences的@Published属性counter - Combine发送
objectWillChange通知,触发SwiftUI的AttributeGraph更新视图 - 此时Sheet已经被从视图树中移除,SwiftUI正在清理相关资源,导致视图Graph中的某个
unowneddelegate引用已经失效,最终触发崩溃
iOS 14.4作为SwiftUI的早期版本,存在不少视图生命周期与状态更新的兼容性bug,尤其是Sheet关闭时的onDisappear执行时机与视图资源清理的顺序问题——这也是你无法复现但用户设备上出现崩溃的核心原因。
可能的触发场景
- 用户快速操作:短时间内重复打开/关闭Sheet,导致视图清理与状态更新的并发冲突
- iOS 14.4系统特定的生命周期bug:Sheet的
onDisappear在视图实例已被部分销毁后才执行,此时触发状态更新会访问失效的引用
修复方案
针对这个问题,最直接有效的修复是避免在Sheet的onDisappear中触发ObservableObject的状态更新,调整执行时机到Sheet关闭之前。具体修改如下:
1. 移除dismissAction和onDisappear,直接在按钮点击时执行回调
修改Sheet的按钮逻辑,先执行状态更新操作,再关闭Sheet:
struct MySheet: View { var changeCounter: () -> Void var changeTimer: () -> Void @Binding var item: ActiveSheet? var body: some View { GeometryReader { metrics in VStack { Button(action: { changeCounter() // 先执行计数器修改 item = nil // 再关闭Sheet }, label: { Text("change_counter") }) Button(action: { changeTimer() // 先执行计时器修改 item = nil // 再关闭Sheet }, label: { Text("change_timer") }) }.frame(width: metrics.size.width, height: metrics.size.height * 0.85) } } }
2. 增加闭包的弱引用捕获(可选,提升安全性)
为了避免极端情况下的引用失效,在传递回调给Sheet时,使用weak self捕获主视图实例:
.sheet(item: $activeSheet) { item in switch item { case .MY_SHEET: MySheet( changeCounter: { [weak self] in self?.changeCounter(decrease: true) }, changeTimer: changeTimer, item: $activeSheet ) } }
3. 针对iOS 14的额外优化(可选)
如果你需要兼容iOS 14,可以考虑在状态更新时增加轻微延迟,避免与视图清理操作冲突:
func changeCounter(decrease: Bool) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { if decrease { preferences.counter -= COUNTER_INTERVAL } } }
验证建议
由于你无法复现该问题,建议在iOS 14.4的模拟器或测试设备上,尝试快速重复打开/关闭Sheet,模拟用户的快速操作,验证修复后的代码是否能避免崩溃。
内容的提问来源于stack exchange,提问作者L3n95
相关产品推荐
相关产品推荐

