阅读 566

【每天学一点】weak & unowned(2021.2.4)

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),开发者也不用特意留意内存管理机制,除了一种特殊的情况:循环强引用,两个对象互相强引用,导致彼此都没法释放。而解决强引用就引出了本文的主角weakunowned,它俩都是为了切断强引用循环而存在的,但是彼此又有些区别。

引用计数只适用于类的实例,struct\enum都是值类型,不是引用类型,不是通过引用的方式存储和传递。

强引用最大的问题就是互相引用,导致计数都没法清零,对象得不到清理,所以解决方案只要一方不强引用另一方就迎刃而解了,途径的话有weakunowned

weakunowned

weakunowned的区别

  • weak在属性对象释放时会将属性设置为nil, 而unowned不会
  • unowned应该翻译为不能在我之前释放,它的生命周期应该要长于当前对象,因为它如果提前释放了,对象再次使用该属性会指向野指针导致崩溃。

举个栗子:

  1. 首先创建三个类
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") }
}
复制代码
  1. 验证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
复制代码
  1. 验证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
复制代码
  1. 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
复制代码

weakunowned解决强循环引用

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,但是要注意该属性不能早于使用者前释放。

源码链接

文章分类
iOS
文章标签