Swift采用ARC自动引用计数管理内存,无需手动管理内存,但闭包、类对象互相持有、可选类型不当使用极易产生循环引用,造成内存泄漏,引发APP卡顿、闪退、OOM崩溃等线上问题。本文结合日常开发高频场景,还原内存泄漏完整复现代码,同时给出weak、unowned、打破引用链等标准解决方案,所有代码可直接在Xcode运行验证,帮助开发者规避常见内存坑。
ARC依靠对象引用计数判断是否释放内存,当A持有B、B同时持有A,双方引用计数永远无法归零,系统无法回收两块内存,形成内存泄漏。可选类型Optional本质是枚举包装,若存储强引用对象,未主动置空同样会延长对象生命周期。
import UIKit
class TestViewController: UIViewController {
var clickCallback: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
// 错误:闭包强持有self,self又持有闭包,循环引用
clickCallback = {
self.doSomething()
}
}
func doSomething() {
print("执行业务逻辑")
}
deinit {
print("控制器正常释放")
}
}页面退出后deinit不会执行,控制器内存永久滞留。
override func viewDidLoad() {
super.viewDidLoad()
clickCallback = { [weak self] in
guard let self = self else { return }
self.doSomething()
}
}weak修饰后不会增加引用计数,页面销毁时可正常释放。
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 = nilclass BankCard {
weak var user: User?
init(user: User) {
self.user = user
}
deinit { print("银行卡对象释放") }
}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,彻底切断持有链。
[weak self],生命周期一定同步时选用unowned;海量精选技术文档和实战案例持续更新,敬请关注【风骏时光少年】