Swift循环引用与内存泄漏避坑实战
Swift循环引用与可选类型内存泄漏避坑实战案例一、文档概述Swift采用ARC自动引用计数管理内存无需手动管理内存但闭包、类对象互相持有、可选类型不当使用极易产生循环引用造成内存泄漏引发APP卡顿、闪退、OOM崩溃等线上问题。本文结合日常开发高频场景还原内存泄漏完整复现代码同时给出weak、unowned、打破引用链等标准解决方案所有代码可直接在Xcode运行验证帮助开发者规避常见内存坑。适用场景页面内闭包回调持有控制器两个Model类互相强引用可选类型延迟赋值引发隐性持有定时器未销毁持续持有对象二、内存泄漏核心原理ARC依靠对象引用计数判断是否释放内存当A持有B、B同时持有A双方引用计数永远无法归零系统无法回收两块内存形成内存泄漏。可选类型Optional本质是枚举包装若存储强引用对象未主动置空同样会延长对象生命周期。三、场景1闭包强引用Self导致泄漏错误示范importUIKitclassTestViewController:UIViewController{varclickCallback:(()-Void)?overridefuncviewDidLoad(){super.viewDidLoad()// 错误闭包强持有selfself又持有闭包循环引用clickCallback{self.doSomething()}}funcdoSomething(){print(执行业务逻辑)}deinit{print(控制器正常释放)}}页面退出后deinit不会执行控制器内存永久滞留。修复方案weak弱引用selfoverridefuncviewDidLoad(){super.viewDidLoad()clickCallback{[weakself]inguardletselfselfelse{return}self.doSomething()}}weak修饰后不会增加引用计数页面销毁时可正常释放。四、场景2两个实体类互相强引用泄漏错误代码classUser{varcard:BankCard?deinit{print(用户对象释放)}}classBankCard{varuser:Userinit(user:User){self.useruser}deinit{print(银行卡对象释放)}}// 调用测试varuser:User?User()varcard:BankCard?BankCard(user:user!)user?.cardcard// 置空外部变量对象仍互相持有无法释放usernilcardnil修复方案一方使用weak可选classBankCard{weakvaruser:User?init(user:User){self.useruser}deinit{print(银行卡对象释放)}}五、场景3可选定时器未销毁造成隐性泄漏classTimerDemo{vartimer:Timer?init(){// Timer强持有selfself持有timertimerTimer.scheduledTimer(withTimeInterval:1,repeats:true){_inself.printLog()}}funcprintLog(){print(定时任务执行)}deinit{print(Timer对象释放)timer?.invalidate()}}仅在deinit销毁定时器为时已晚对象早已无法释放。正确写法classTimerDemo{vartimer:Timer?init(){timerTimer.scheduledTimer(withTimeInterval:1,repeats:true){[weakself]_inguardletselfselfelse{return}self.printLog()}}funcstopTimer(){timer?.invalidate()timernil}deinit{print(Timer对象释放)}}页面销毁前主动调用stopTimer()同时闭包弱引用self彻底切断持有链。六、排查与开发规范开发时使用Xcode内存图工具查看对象引用链路定位泄漏闭包捕获列表优先使用[weak self]生命周期一定同步时选用unowned双向关联Model从属一方必须用weak修饰定时器、通知、网络回调等场景提前销毁资源并打破持有可选类型存储长生命周期对象页面销毁手动置空Optional变量。海量精选技术文档和实战案例持续更新敬请关注【风骏时光少年】