weak & unowned
学习素材
首先聊下为什么要有内存管理机制,一般对象创建于堆上,如果不回收利用,一直创建,内存很快就被耗完了,为了回收利用,系统需要将没有继续使用的那些对象给回收,怎么鉴定哪些可以回收哪些不可以回收,于是就捣鼓出了引用计数的原理。
引用计数的规则很简单,持有计数加1,放弃持有计数减1,在远古的MRC(Mannual Reference Counting)时代, 增加计数的方式有:
- 创建对象
[[XX alloc] init] - retain对象
[object retain] - copy对象(浅拷贝)
[object copy] - 设置属性
@property (nonatomic, retain) UIWindow *window;或@property (nonatomic, copy) UIWindow *window;
放弃持有的方式:
- relase
[object release]
时间回到2021年,现在MRC应该已经绝迹,项目默认采用的ARC(Auto Reference Counting),开发者也不用特意留意内存管理机制,除了一种特殊的情况:循环强引用,两个对象互相强引用,导致彼此都没法释放。而解决强引用就引出了本文的主角weak和unowned,它俩都是为了切断强引用循环而存在的,但是彼此又有些区别。
引用计数只适用于类的实例,struct\enum都是值类型,不是引用类型,不是通过引用的方式存储和传递。
强引用最大的问题就是互相引用,导致计数都没法清零,对象得不到清理,所以解决方案只要一方不强引用另一方就迎刃而解了,途径的话有weak和unowned。
weak和unowned
weak和unowned的区别
weak在属性对象释放时会将属性设置为nil, 而unowned不会unowned应该翻译为不能在我之前释放,它的生命周期应该要长于当前对象,因为它如果提前释放了,对象再次使用该属性会指向野指针导致崩溃。
举个栗子:
- 首先创建三个类
class ModelA1 {
weak var modelB: ModelB?
deinit { print("ModelA1 is being deinitialized") }
}
class ModelA2 {
unowned var modelB: ModelB?
deinit { print("ModelA2 is being deinitialized") }
}
class ModelB {
deinit { print("ModelB is being deinitialized") }
}
- 验证weak在属性对象释放后会设置为nil
// 示例1: 展示weak的作用
// weak属性在关联对象被释放后, 将其值设置为nil
// 所以weak标记的属性必须是可选值<Optional>
var modelB1:ModelB? = ModelB()
let modelA1:ModelA1? = ModelA1()
print("modelA1.modelB:\(String(describing: modelA1?.modelB))") // modelA1.modelB:nil
modelA1?.modelB = modelB1
print("modelA1.modelB:\(String(describing: modelA1?.modelB))") // modelA1.modelB:Optional(SwiftDemo.ModelB)
modelB1 = nil
print("modelA1.modelB:\(String(describing: modelA1?.modelB))") // modelA1.modelB:nil
- 验证unknowned在属性对象释放后再次访问会引发崩溃
// 示例2: unknowned应该翻译为"不能在我之前释放"
// 假设unknowned属性在当前对象之前释放了, 当前对象引用它就会崩溃, 因为当前对象并没有将属性设置为nil
// 指针指向了一个野指针
var modelB2: ModelB? = ModelB()
let modelA2: ModelA2? = ModelA2()
print("modelA2.modelB:\(String(describing: modelA2?.modelB))") // modelA2.modelB:nil
modelA2?.modelB = modelB2
print("modelA2.modelB:\(String(describing: modelA2?.modelB))") // modelA2.modelB:Optional(SwiftDemo.ModelB)
modelB2 = nil
print("modelA2.modelB:\(String(describing: modelA2?.modelB))") // Fatal error: Attempted to read an unowned reference but object 0x600002a14020 was already deallocated
- weak和unowned的对象在主体对象释放后也会释放
// 示例3: 不论是weak还是unowned, 在没有互相引用的情况下
// 当主体对象释放后, 属性对象也会在没有其他对象强引用的情况下进行释放
let modelB3:ModelB? = ModelB()
var modelA3:ModelA1? = ModelA1()
print("modelA3.modelB:\(String(describing: modelA3?.modelB))") // modelA1.modelB:nil
modelA3?.modelB = modelB3
print("modelA1.modelB:\(String(describing: modelA3?.modelB))") // modelA1.modelB:Optional(SwiftDemo.ModelB)
modelA3 = nil
// 输出: ModelA1 is being deinitialized
print("-----modelA3 = nil End------")
// 当前作用域结束后, 由于没有对象强引用ModelB3, ModelB3也将释放: ModelB is being deinitialized
let modelB4: ModelB? = ModelB()
var modelA4: ModelA2? = ModelA2()
print("modelA4.modelB:\(String(describing: modelA4?.modelB))") // modelA2.modelB:nil
modelA4?.modelB = modelB4
print("modelA4.modelB:\(String(describing: modelA4?.modelB))") // modelA2.modelB:Optional(SwiftDemo.ModelB)
modelA4 = nil
print("-----modelA4 = nil End------")
// 当前作用域结束后, 由于没有对象强引用ModelB4, ModelB4也将释放: ModelB is being deinitialized
weak和unowned解决强循环引用
class Student {
var name: String
var card: StudentCard?
init(name: String) {
self.name = name
self.card = StudentCard(student: self)
print("init Student")
}
deinit {
print("deinit Student")
}
}
class StudentCard {
unowned var student: Student
init(student: Student) {
self.student = student
print("init StudentCard")
}
deinit {
print("deinit StudentCard")
}
}
class Student1 {
var name: String
var card: StudentCard1?
init(name: String) {
self.name = name
self.card = StudentCard1(student: self)
print("init Student1")
}
deinit { print("deinit Student1") }
}
class StudentCard1 {
weak var student: Student1?
init(student: Student1?) {
self.student = student
print("init StudentCard1")
}
deinit { print("deinit StudentCard1") }
}
// 声明类属性
var stu: Student?
var stu1: Student1?
// 方法中调用:
// 使用unowned引用学生
// 学生可能没有卡(如丢卡), 卡片一定要有学生
stu = Student(name: "Bill")
print("name:\(String(describing: stu?.name)) card:\(String(describing: stu?.card))")
stu = nil
print("part I ends")
// 使用weak引用学生
stu1 = Student1(name: "Bill")
print("name:\(String(describing: stu1?.name)) card:\(String(describing: stu1?.card))") // name:Optional("Bill") card:Optional(SwiftDemo.StudentCard1)
stu1 = nil
print("part II ends")
// 输出:
// init StudentCard
// init Student
// name:Optional("Bill") card:Optional(SwiftDemo.StudentCard)
// deinit Student
// deinit StudentCard
// part I ends
// init StudentCard1
// init Student1
// name:Optional("Bill") card:Optional(SwiftDemo.StudentCard1)
// deinit Student1
// deinit StudentCard1
// part II ends
另一种强引用的场景: 使用了self的闭包属性
解决方案也是通过[weak self] 或者 [unowned self]
lazy var someClosure = {
[unowned self, weak delegate = self.delegate]
(index: Int, stringToProcess: String) -> String in
// 这里是闭包的函数体
}
总结
- 对比下来感觉没啥必要使用unowned还白白增加的崩溃的风险。除非你真的确定所仰赖的大佬属性不会提前释放(但是Bug就是这么奇怪,谁知道他什么时候就抽风了呢),所以建议优先使用weak。
- weak属性要求一定是optional的,如果场景要求非optional的,可以考虑使用unowned,但是要注意该属性不能早于使用者前释放。