You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift调用子视图控制器偶发致命错误排查求助

偶发子视图控制器崩溃问题排查:已释放的unowned引用错误

问题概述

地图页面长按打开子视图控制器TripOverviewVC时,偶发崩溃,控制台错误:

Fatal error: Attempted to read an unowned reference but object 0x283f3cf80 was already deallocated

崩溃触发概率极低,需多次长按复现,无法通过断点调试。添加日志后发现:正常场景下CHECK 1-16顺序输出,崩溃时卡在CHECK 14与CHECK 15之间;子VC的viewWillAppear(输出CHECK 10)可正常触发,但viewDidAppear(CHECK 15)从未执行。

相关代码

父VC调用子VC代码

let slideVC = TripOverviewVC(customer: myCustomer, route: myRoute, panelController: panelController!, annotationManager: annotationManager!, mapview: mapView!)
slideVC.view.roundCorners([.topLeft, .topRight], radius: 22)
slideVC.view.layer.zPosition = 15
print("CHECK 8")
add(slideVC)
print("CHECK 13")

let height = view.frame.height
let width = view.frame.width
slideVC.view.frame = CGRectMake(0, self.view.frame.maxY, width, height)
print("CHECK 14")

父VC的add子VC实现

func add(_ child: UIViewController) {
    addChild(child)
    print("CHECK 9")
    view.addSubview(child.view)
    print("CHECK 11")
    child.didMove(toParent: self)
    print("CHECK 12")
}

子VC的viewDidAppear实现

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    print("CHECK 15")
    UIView.animate(withDuration: 0.3) { [weak self] in
        let frame = self?.view.frame
        let yComponent = UIScreen.main.bounds.height - self!.view.frame.height*0.5
        self?.view.frame = CGRectMake(0, yComponent, frame!.width, frame!.height)
    }
    tmpOrigin = self.view.frame.origin
    print("CHECK 16")
}

问题分析

  1. 核心触发点:unowned引用访问已释放对象
    崩溃提示明确指向unowned修饰的对象已被回收,大概率是TripOverviewVC内部用unowned声明了某些属性(比如初始化时传入的panelController、annotationManager、mapView),且该对象的生命周期短于子VC,导致在流程中被访问时已被系统释放。

  2. 生命周期异常:viewDidAppear未触发
    子VC的viewWillAppear触发但viewDidAppear未执行,说明视图未完成完整的显示流程,可能是子VC在布局阶段被意外释放,或者视图层级操作打断了生命周期回调链。

  3. 代码隐性隐患

  • 初始化子VC时使用强制解包(panelController!等),虽不是当前偶发崩溃的直接原因,但存在nil崩溃风险;
  • viewDidAppear的动画闭包中,使用[weak self]后又强制解包self!,若self被释放会触发崩溃;
  • 父VC未显式持有子VC,仅通过addChild关联,内存紧张时可能被系统提前回收。

解决方案

  1. 排查并修复unowned引用
    检查TripOverviewVC中所有unowned修饰的属性,将非必要的unowned改为weak(需改为可选类型),或确保被引用对象的生命周期长于子VC:
// 原可能的unowned声明
unowned var panelController: PanelController
// 改为weak
weak var panelController: PanelController?
  1. 修复强制解包问题
    修改viewDidAppear中的动画闭包,避免强制解包:
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    print("CHECK 15")
    UIView.animate(withDuration: 0.3) { [weak self] in
        guard let self = self else { return }
        let frame = self.view.frame
        let yComponent = UIScreen.main.bounds.height - frame.height*0.5
        self.view.frame = CGRect(x: 0, y: yComponent, width: frame.width, height: frame.height)
    }
    tmpOrigin = view.frame.origin
    print("CHECK 16")
}
  1. 确保父VC强持有子VC
    在父VC中添加属性存储子VC,避免被提前释放:
// 父VC中声明
private var currentSlideVC: TripOverviewVC?

// 调用时赋值
currentSlideVC = TripOverviewVC(customer: myCustomer, route: myRoute, panelController: panelController!, annotationManager: annotationManager!, mapview: mapView!)
let slideVC = currentSlideVC!
// 后续操作不变...
  1. 调整子VC添加流程
    将frame设置逻辑移至子VC内部(如viewWillLayoutSubviews),避免父VC直接操作子VC视图导致生命周期异常:
// TripOverviewVC的viewWillLayoutSubviews
override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    let screenBounds = UIScreen.main.bounds
    view.frame = CGRect(x: 0, y: screenBounds.maxY, width: screenBounds.width, height: screenBounds.height)
}

内容的提问来源于stack exchange,提问作者Jan L

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 03:45:53