写 Swift 时,你有没有遇到过这种状况:界面关了,但内存占用没降;定时器还在跑,数据却已经没了;或者一个 ViewController 明明 pop 了,它的 deinit 就是不执行?十有八九,是引用关系在‘捣鬼’。
强引用:默认的‘死拽不放’
Swift 默认所有对象赋值都是强引用。比如:
class Person {
let name: String
init(name: String) { self.name = name }
}
let p1 = Person(name: "小王")
let p2 = p1 // p2 强引用 p1
只要还有至少一个强引用指着它,这个对象就一直活在内存里——哪怕你心里早把它‘拉黑’了。
弱引用:说好‘不拦着你走’
当两个对象互相强引用,又没有外部干预,就会卡死在内存里,形成循环引用(retain cycle)。比如常见的 delegate 场景:
class ViewController {
var dataSource: DataSource? // 强引用
func loadData() {
dataSource?.fetch()
}
}
class DataSource {
weak var delegate: ViewController? // 关键:weak!
func fetch() {
delegate?.updateUI() // 安全调用
}
}这里把 delegate 声明为 weak var,意思是:“我只临时看看你,你不在我也不拦着,你走了我自动变成 nil。”这样 ViewController pop 后能正常释放,DataSource 也不会拖着它不放。
什么时候必须用 weak?
记住这三种典型场景:
- 代理(delegate):比如
UITableViewDelegate、自定义回调对象; - 闭包中捕获
self:如果闭包被对象长期持有(如网络请求 completion),又反过来被self持有,就得用[weak self]; - 父-子视图关系里的反向引用:比如子 View 想通知父 View,父 View 是强引用它的,那子 View 的回调属性就得是
weak。
举个闭包例子:
class NetworkManager {
func request(completion: @escaping (Data) -> Void) {
// 模拟异步请求
DispatchQueue.main.async {
let data = Data()
completion(data)
}
}
}
class ViewController: UIViewController {
let manager = NetworkManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.request { [weak self] data in
guard let self = self else { return }
self.update(with: data) // 防止 self 已释放
}
}
func update(with data: Data) { /* ... */ }
}加了 [weak self],闭包就不会强留 ViewController 在内存里。配合 guard let self = self,还能安全解包,避免崩溃。
weak 不是万能的,unowned 要小心
有人会问:为啥不用 unowned?它不也绕过强引用吗?是,但它不安全——一旦对象提前释放,再访问就直接 crash。除非你 100% 确定生命周期,否则优先选 weak + 可选绑定,更稳。
装机要挑兼容配件,写代码也得配对引用类型。强引用管‘活着’,弱引用管‘放手’,搞清谁该拽着、谁该松手,内存才不会越积越厚,App 才跑得轻快利落。