iOS开发 - 「Swift 学习」Swift中的循环强引用

Swift中使用(RAC)自动引用计数机制,实例赋值给属性、常量或变量,都会创建此实例的强引用,只要强引用存在,实例是不允许被销毁的

实例间的循环强引用

当两个实例互相保持对方的强引用,并让对方不会被销毁,就会造成循环强引用。
例:

    class School{
        let classNum:Int
        init(classNum:Int){
            self.classNum = classNum
        }
        var student:Student?
        deinit{
            print("\(classNum)班,被析构")
        }
    }
    class Student {
        var name:String = ""
        init(name:String){
            self.name = name
        }
        var school:School?
        init(closure: @escaping (_ student: Student) -> Void) {
            closure(self)
        }
        deinit{
            print("\(name)" + "被析构")
        }
    }

调用

//定义初始化为nil的属性
    var  xiaoMing:Student?
    var  sunMiddleSchool:School?
    func setValue(){
        //赋值
        xiaoMing = Student(name: "小明")
        sunMiddleSchool = School(classNum: 3)
        
        //对可选实例xiaoMing和sunMiddleSchool中的school、student属性赋值(此时会造成循环强引用)
        xiaoMing?.school = sunMiddleSchool
        sunMiddleSchool?.student = xiaoMing
        
        //释放实例xiaoMing和sunMiddleSchool变量持有的强引用时,引用计数不会降为0,实例也并不会被ARC销毁,析构函数也并不会被调用
        //此时实例间的循环强引用阻止了实例销毁,并会造成内存泄漏
        xiaoMing = nil
        sunMiddleSchool = nil
    }

解决实例间的循环强引用

Swift中提供了弱引用、无主引用两种方法解决使用类的属性遇到的循环强引用问题

弱引用

对于生命周期中会变为nil的实例使用弱引用,通过在定义类属性语句前添加“weak”关键字声明弱引用类属性

class School{
        let classNum:Int
        init(classNum:Int){
            self.classNum = classNum
        }
        //使用"weak"关键字定义弱引用实例属性
        weak  var student:Student?
        deinit{
            print("\(classNum)班,被析构")
        }
    }
    class Student {
        var name:String = ""
        init(name:String){
            self.name = name
        }
        //使用"weak"关键字定义弱引用实例属性
        weak  var school:School?
        init(closure: @escaping (_ student: Student) -> Void) {
            closure(self)
        }
        deinit{
            print("\(name)" + "被析构")
        }
    }

调用

//定义初始化为nil的属性
    var  xiaoMing:Student?
    var  sunMiddleSchool:School?
    func setValue(){
        //赋值
        xiaoMing = Student(name: "小明")
        sunMiddleSchool = School(classNum: 3)
        
        //对可选实例xiaoMing和sunMiddleSchool中的school、student属性赋值(此时形成的是循环弱引用)
        xiaoMing?.school = sunMiddleSchool
        sunMiddleSchool?.student = xiaoMing
        
        //释放实例xiaoMing和sunMiddleSchool变量持有的弱引用时,引用计数降为0,实例会被ARC销毁,析构函数会被调用
        xiaoMing = nil
        sunMiddleSchool = ni
    }

调用结果

小明被析构
3班,被析构

无主引用

对于初始化赋值后再也不会被赋值为nil的实例,使用无主引用。

闭包引起的循环强引用

例:

class HTMLElement {
    let name:String
    let text:String?
    
    lazy var asHTML:() -> String = {
        if let text = self.text{
            return "<\(self.name)>\(text)</\(self.name)>"
        }else {
            return "<\(self.name) />"
        }
    }
    
    init(name:String,text:String? = nil){
        self.name = name
        self.text = text
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

调用

func HTMLElementFunc(){
    var paragraph:HTMLElement? = HTMLElement(name: "p", text: "hello,world")
    //HTMLElement 类产生了类实例和 asHTML 默认值的闭包之间的循环强引用
    print(paragraph!.asHTML())
    paragraph = nil
}

调用结果

<p>hello,world</p>

从调用结果看出:析构函数并未被调用说明paragraph实例并没有被释放,实例的 asHTML 属性持有闭包的强引用。但是,闭包在其闭包体内使用了self(引用了self.name和self.text),因此闭包捕获了self,这意味着闭包又反过来持有了HTMLElement实例的强引用。这样两个对象就产生了循环强引用.

解决闭包引起的循环强引用:在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。

弱引用

1.当闭包和捕获的实例总是互相引用时并且总是同时销毁时,将闭包内的捕获定义为无主引用。
2.相反的,当捕获引用有时可能会是nil时,将闭包内的捕获定义为弱引用。
3.如果捕获的引用绝对不会置为nil,应该用无主引用,而不是弱引用

无主引用

通过在定义闭包时同时定义捕获列表[unowned self] in作为闭包的一部分,来解决闭包和类实例之间的循环强引用。

class HTMLElement {
    let name:String
    let text:String?
    
    lazy var asHTML:() -> String = {
    C  //定义闭包时同时定义捕获列表
        [unowned self] in
        if let text = self.text{
            return "<\(self.name)>\(text)</\(self.name)>"
        }else {
            return "<\(self.name) />"
        }
    }
    
    init(name:String,text:String? = nil){
        self.name = name
        self.text = text
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}

调用

func HTMLElementFunc(){
    var paragraph:HTMLElement? = HTMLElement(name: "p", text: "hello,world")
    print(paragraph!.asHTML())
    paragraph = nil
}

调用结果

<p>hello,world</p>
p is being deinitialized

根据调用结果分析:此时的析构函数被调用,实例paragraph已被释放销毁

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容