Swift循环引用与内存泄漏避坑实战

Swift循环引用与可选类型内存泄漏避坑实战案例

一、文档概述

Swift采用ARC自动引用计数管理内存,无需手动管理内存,但闭包、类对象互相持有、可选类型不当使用极易产生循环引用,造成内存泄漏,引发APP卡顿、闪退、OOM崩溃等线上问题。本文结合日常开发高频场景,还原内存泄漏完整复现代码,同时给出weak、unowned、打破引用链等标准解决方案,所有代码可直接在Xcode运行验证,帮助开发者规避常见内存坑。

适用场景

  1. 页面内闭包回调持有控制器
  2. 两个Model类互相强引用
  3. 可选类型延迟赋值引发隐性持有
  4. 定时器未销毁持续持有对象

二、内存泄漏核心原理

ARC依靠对象引用计数判断是否释放内存,当A持有B、B同时持有A,双方引用计数永远无法归零,系统无法回收两块内存,形成内存泄漏。可选类型Optional本质是枚举包装,若存储强引用对象,未主动置空同样会延长对象生命周期。

三、场景1:闭包强引用Self导致泄漏(错误示范)

import UIKit

class TestViewController: UIViewController {
    var clickCallback: (() -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 错误:闭包强持有self,self又持有闭包,循环引用
        clickCallback = {
            self.doSomething()
        }
    }
    
    func doSomething() {
        print("执行业务逻辑")
    }
    
    deinit {
        print("控制器正常释放")
    }
}

页面退出后deinit不会执行,控制器内存永久滞留。

修复方案:weak弱引用self

override func viewDidLoad() {
    super.viewDidLoad()
    clickCallback = { [weak self] in
        guard let self = self else { return }
        self.doSomething()
    }
}

weak修饰后不会增加引用计数,页面销毁时可正常释放。

四、场景2:两个实体类互相强引用泄漏

错误代码

class User {
    var card: BankCard?
    deinit { print("用户对象释放") }
}

class BankCard {
    var user: User
    init(user: User) {
        self.user = user
    }
    deinit { print("银行卡对象释放") }
}

// 调用测试
var user: User? = User()
var card: BankCard? = BankCard(user: user!)
user?.card = card
// 置空外部变量,对象仍互相持有,无法释放
user = nil
card = nil

修复方案:一方使用weak可选

class BankCard {
    weak var user: User?
    init(user: User) {
        self.user = user
    }
    deinit { print("银行卡对象释放") }
}

五、场景3:可选定时器未销毁造成隐性泄漏

class TimerDemo {
    var timer: Timer?
    
    init() {
        // Timer强持有self,self持有timer
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            self.printLog()
        }
    }
    
    func printLog() {
        print("定时任务执行")
    }
    
    deinit {
        print("Timer对象释放")
        timer?.invalidate()
    }
}

仅在deinit销毁定时器为时已晚,对象早已无法释放。

正确写法

class TimerDemo {
    var timer: Timer?
    
    init() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
            guard let self = self else { return }
            self.printLog()
        }
    }
    
    func stopTimer() {
        timer?.invalidate()
        timer = nil
    }
    
    deinit {
        print("Timer对象释放")
    }
}

页面销毁前主动调用stopTimer(),同时闭包弱引用self,彻底切断持有链。

六、排查与开发规范

  1. 开发时使用Xcode内存图工具,查看对象引用链路定位泄漏;
  2. 闭包捕获列表优先使用[weak self],生命周期一定同步时选用unowned
  3. 双向关联Model,从属一方必须用weak修饰;
  4. 定时器、通知、网络回调等场景,提前销毁资源并打破持有;
  5. 可选类型存储长生命周期对象,页面销毁手动置空Optional变量。

海量精选技术文档和实战案例持续更新,敬请关注【风骏时光少年】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风骏时光少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值