Swift 自动引用计数(ARC)

Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存。通常情况下,Swift 的内存管理机制会一直起着作用,你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。

自动引用计数的工作机制

当你每次创建一个类的新的实例的时候,ARC 会分配一大块内存用来储存实例的信息。内存中会包含实例的类型信息,以及这个实例所有相关属性的值。

此外,当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。

然而,当 ARC 收回和释放了正在被使用中的实例,该实例的属性和方法将不能再被访问和调用。实际上,如果你试图访问这个实例,你的应用程序很可能会崩溃。

为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。

为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢的保持住,只要强引用还在,实例是不允许被销毁的。


自动引用计数主要有两大贡献如下:

        1:跟踪内存中的实例被哪些类属性,常量或者变量所引用.
        2:在没有类属性,常量或者变量引用该实例时,释放该实例在内存中所占的空间.

强引用

       在 Swift中,当我们把一个类实例赋值给一个类属性,常量或者变量时,Swift都会自动帮我们创建一个类属性,常量或者变量到该实例的引用,该引用确保了 ARC 不会析构此类的实例,我们把这种确保 ARC 不自动析构该实例的引用叫做强引用.

       强引用的用法: 年轻都有一个江湖梦,快意恩仇,一把长剑满腹豪情,如杨过,郭靖等都是大侠,当然也有一类人是被江湖所唾弃,比如恶贯满盈段延庆,下面使用段延庆来说明强引用的用法.

class People{

    var type: String
    var name: String
    
    //指定构造器方法,设置名称和类型
    init(name: String ,type: String){
    
        self.name = name
        self.type = type
        
        print("\(type)\(name) 诞生于江湖!")
    }
    
    //便捷构造器.
    convenience init(){
    
        self.init( name:"无名氏",type: "江湖好汉")
    }
    
    //析构器方法,该类的实例被 ARC 回收时调用
    deinit{
    
        print("该实例被回收,内存释放,也就是说\(self.name)离开江湖,不再存在了!")
    }
}

//该部分可以打断点测试
func testOne(){
    
        //定义一个名为段延庆的实例.  这里注意一点duanYanQing是可选类型,因为后面我们需要设置为nil;来告诉 ARC, 我们不使用该实例了.
        var  duanYanQing: People? = People(name: "段延庆", type: "大恶人")
        
        //大理王子也是段延庆,所以daLiWangZi变量指向了duanYanQing这个实例.
        var daLiWangZi = duanYanQing
        
        //恶人之首也是段延庆,所以eRenZhiShou变量指向了段延庆这个实例.
        var eRenZhiShou = duanYanQing
        
        //大理王子被除名,不复存在,所以设置为nil.
        daLiWangZi = nil
        
        //恶人之首也不复存在,所以设置为nil
        eRenZhiShou = nil
        
        //段延庆也不复存在,所以设置为nil
        duanYanQing = nil
        
 /*
 打印结果:
         大恶人段延庆 诞生于江湖!
         该实例被回收,内存释放,也就是说段延庆离开江湖,不再存在了!
 分析:

     1:在代码中相继定义了两个变量daLiWangZi和eRenZhiShou,它们在赋值的过程中会延续duanYanQing的类型,也是 People? .
     2:虽然有三个变量指向了同一个 People实例,但是因为指向同一个实例,所以构造器方法只调用了一次.
     3:当3个变量全都设置为nil,也就是段延庆渣江湖上没有任何名号了,也就是该实例被 ARC 回收了,内存被销毁了.
     4:以上3个变量都指向段延庆的这个People实例, ARC会为其生成三个引用.有三个强引用指向它,所以ARC不会收回这块内存,只有当这3条强引用全部消除时,这个实例才会被销毁,内存才会被回收.
        
 */
} 
2: 闭环与解环

//男生类
class Male {
    
    let name: String
    //每个男人都可能有女朋友也可能没有女朋友
    var girlFriend: Female?
    
    init(name: String){
        
        self.name = name
    }
    
    deinit{
        
        print("男人\(name)实例被销毁")
    }
}

//女生类
class Female {
    
    let name: String
    //当然女生也一样,不见得每个女生都有男朋友
    var boyFriend: Male?
    
    init(name: String){
        
        self.name = name
    }
    
    deinit{
        
        print("女人\(name)实例被销毁")
    }
    
}

func testTwo(){
    
        //男人jack出场
        var man:Male? = Male(name: "jack")
        
        //女人rose出场
        var woman: Female? = Female(name: "rose")
        
        //man中的girlFriend指向woman,强引用,woman中的boyFriend指向man,所以产生了强引用环.
        man!.girlFriend = woman
        woman!.boyFriend = man
        
        //如果man和woman这两个实例不再使用了,我们给其赋值为nil,让 ARC回收
        print("jack和rose完成了人生使命,我们告诉 ARC 这两个实例不再使用了!")
        man = nil
        woman = nil

        print("不知道这两个实例被回收了没有!")
        
        /*
        
        打印结果:
        jack和rose完成了人生使命,我们告诉 ARC 这两个实例不再使用了!
        不知道这两个实例被回收了没有!
        
        分析:
           虽然我们将man,woman设置为nil,告诉 ARC 这两个实例不用了,可是上面结果并没有显示实例被回收的信息,说明 ARC 并没有正确地回收.这就是我们经常遇到的问题,强引用环.为什么呢?
           当 ARC 发现 man变量和woman变量被设置为nil,准备释放对象,但是却发现 man中的girlFriend指向woman,所以等待woman释放.同理woman也要等待 man释放,这样就出现了相互等待的现象.所以造成对象没有被释放.
        
        为了解决强引用环,Swift提供了3种方法:弱引用,无主引用,捕获列表,并且分别对应不同的使用场景.
        1:如果被指向的实例有可能为nil,则使用弱引用.
        2:如果被指向的实例不为nil,则使用无主引用.
        3:如果在类属性使用闭包时,并且闭包体内引用当前实例self而产生强引用环时,则使用捕获列表.

        */
        
        
 }
3:弱引用  改变之前的Male类和函数部分,Female代码不动.

class Male {
    let name: String
    //每个男人都可能有女朋友也可能没有女朋友
    weak var girlFriend: Female?
    
    init(name: String){
        
        self.name = name
    }
    
    deinit{
        
        print("男人\(name)实例被销毁")
    }
} 

func testThree(){
    
        //男人jack出场
        var man:Male? = Male(name: "jack")
        
        //女人rose出场
        var woman: Female? = Female(name: "rose")
        
        //man中的girlFriend指向woman,强引用,woman中的boyFriend指向man
        //所以产生了强引用环.
        man!.girlFriend = woman
        woman!.boyFriend = man
        
        //如果man和woman这两个实例不再使用了,我们给其赋值为nil,让 ARC回收
        print("jack和rose完成了人生使命,我们告诉 ARC 这两个实例不再使用了!")
       
        // print("woman实例释放前, man.girlFriend的值:\(man!.girlFriend)")
        //woman = nil
        // print("woman实例释放后, man.girlFriend的值:\(man!.girlFriend)")
        
        print("man实例释放后, woman!.boyFriend的值:\(woman!.boyFriend)")
        man = nil
        print("man实例释放前, woman!.boyFriend的值:\(woman!.boyFriend)")
      
        woman = nil
        
        /*

        打印:
        jack和rose完成了人生使命,我们告诉 ARC 这两个实例不再使用了!
        woman实例释放前, man.girlFriend的值:Optional(ARC.Female)
        女人rose实例被销毁
        woman实例释放后, man.girlFriend的值:nil
        男人jack实例被销毁
        
        jack和rose完成了人生使命,我们告诉 ARC 这两个实例不再使用了!
        man实例释放后, woman!.boyFriend的值:Optional(ARC.Male)
        man实例释放前, woman!.boyFriend的值:Optional(ARC.Male)
        女人rose实例被销毁
        男人jack实例被销毁
        
        分析:
        1:经过2次的打印, Swift会主动维护弱引用的值,如man实例中的弱引用girlFriend属性,它指向woman属性,所以当woman实例释放,则girlFriend值会被 Swift重置为nil.
        2:强引用和弱引用是不一样的,在强引用结果中,我们可以看到woman的强引用属性boyFriend指向的man实例,不管其释放语法,这个boyFriend的值不会改变!
        3:弱引用的定义格式为: weak var 属性.并且注意了弱引用必须是变量类型,因为弱引用的值在运行时会改变.
        4:如果某个属性指向的实例有可能为nil,则我们使用弱引用类型.
        */
    }
4:解环妙法无主引用

  上一个例子中,man实例中的girlFriend属性是一个可选类型,因此我们可以把它定义为一个弱引用,当所有的强引用都注销时,Swift会自动帮我们把这个弱引用改成nil,同时注销整个实例,但如果这个指向类实例的引用不是可选类型,不能够赋值为nil,我们就需要使用无主引用了.
 
  无主引用是由关键字unowned修饰的引用,它和弱引用在用途上完全一样,都是用来打破强引用环,但和弱引用的最大不同是,无主引用总是假定其引用对象一直存在(有值),因而有主引用不能修饰可选类型(注意:在实例被销毁后,还打算通过它的无主引用访问该实例,那么程序会奔溃的).简单来说,只要两个类实例间有形成强引用环的可能,并且满足以下场景,都应该使用关键字unowned修饰类属性:

  1:其中一个类实例中的属性为可选类型,即其值可以为nil,而另外一个实例中的属性为非可选类型,不能为 nil.
  2:两个类实例中的属性,一旦初始化后,都不能为nil.

场景一 :假设有成年人和小孩这两个类,在成年人这个类中有小孩这个属性,小孩这个类里有保留了成年人这个属性,所以它们之间存在行程强引用环的可能性.因此,我们需要弱引用或者无主引用来打破.但是到底是弱引用还是无主引用呢,来看一下关系,这这两个类关系的模型中,对于成年人来说,小孩是个可选属性,因为并不是所有成年人都决定要小孩,但对于小孩来说,成年人作为监护人在小孩的成长中是必须的,所以我们使用无主引用.

class Adult{

    let name: String
    init(name: String){
    
        self.name = name
    }
    
    var child: Child?
    deinit{
    
        print("Adult \(name) 被销毁了!")
    }
}

class Child{

    let name: String
    unowned var guardian: Adult  //无主引用既可以用于常量也可以是变量
    init(name: String, guardian: Adult){
    
        self.name = name
        self.guardian = guardian
    }
    
    deinit{
    
        print("Child \(name) 被销毁了!")
    }
    
}
func testFour(){
        //记住:可选变量自动初始化为 nil
        var stark: Adult?
        stark = Adult(name: "斯塔克")
        stark!.child = Child(name: "兰博", guardian: stark!)
        stark = nil
        
        /*
        Adult 斯塔克 被销毁了!
        Child 兰博 被销毁了!
        
        Child实例中保存的是Adult实例的无主引用,所以当斯塔克被置nil之后,就再没有强引用指向Adult实例,所以Adult类的实例被 ARC 自动回收,同时Adult实例到Child实例的强引用也潇洒了,由于Child实例再也没有指向它的强引用,所以兰博也被销毁了.
        */
}
5:解环妙法捕获列表   其实也就是类实例和闭包之间的强引用环的处理.

强引用环不是只存在于两个引用类型之间吗?那么怎么又和闭包有关系呢?因为闭包也是引用类型,所以当你把一个闭包赋值给一个类属性时, Swift会帮你创建一个该类实例到闭包的强引用,又由于闭包能够捕获上下文中的变量,所以当你在该闭包内部调用该类实例的属性或者类方法时,闭包将会捕获该类实例的属性self,从而导致该闭包到该类实例的强引用也被创建,最终造成了类实例的闭包之间的强引用环. 

//下面看一个天气预报的例子
class WeatherReport{

    let location: String
    let weather: String
    var temperature: Int? //定义为可选,并不是所有人都关系温度.
    
    //因为该计算属性使用到闭包,并且闭包中使用到了self,所以必然是惰性属性.
    lazy var reports:() -> String = { //使用闭包赋值给reports属性,该属性类型为() -> String(可以理解为匿名函数也就是闭包).
       
        if self.temperature != nil{
        
            return "\(self.location)的天气预报是:\(self.weather),气温是:\(self.temperature)"
        }else{
    
            return "\(self.location)的天气预报是:\(self.weather)"
        }
    }
    
    init(location: String, weather: String){
    
        self.location = location
        self.weather = weather
    }
    
    //如果该实例可以被 ARC 回收,则必然调用,否则说明该实例依然存在.
    deinit{
     
        print("\(location)的天气预报实例被销毁了!")
    }
}
func testFive(){
    
        var weatherReport:WeatherReport? = WeatherReport(location: "成都", weather: "多云")
        print("\(weatherReport!.reports())")

        //这句话告诉 ARC 该类可以被回收了.
        weatherReport = nil
        
        /*
        打印结果:
        成都的天气预报是:多云

        分析:
        1:当使用闭包去初始化一个类属性时,该属性前必须加lazy进行修饰,否则将不能够在闭包体内访问该类中的其他属性和方法,因为当闭包段代码被执行时,类是实例还没有完成初始化,self不起作用,另外及时加了 lazy修饰符,在闭包中不能隐式地访问self属性,必须显示的写出来.
        2:在执行weatherReport = nil代码后,我们告诉 ARC 这个实例不用了,但是我们并没有获得相应的打印信息,因为有强引用环存在,那就是() -> String闭包任然作为强引用指向weatherReport的实例,所以 ARC 放弃了回收,从而造成内存泄露.
        */
 }

//现在改成如下代码:
class WeatherReport{
    let location: String
    let weather: String
    var temperature: Int? //定义为可选,并不是所有人都关系温度.
    
    //因为该计算属性使用到闭包,并且闭包中使用到了self,所以必然是惰性属性.
//    lazy var reports:() -> String = { //使用闭包赋值给reports属性,该属性类型为() -> String(可以理解为匿名函数也就是闭包).
//        [unowned self] in
//        if self.temperature != nil{
//        
//            return "\(self.location)的天气预报是:\(self.weather),气温是:\(self.temperature)"
//        }else{
//    
//            return "\(self.location)的天气预报是:\(self.weather)"
//        }
//    }
    
    /*
      注意:如果闭包体当前实例未来永远不会为nil,那么使用无主类型,否则使用弱引用捕获类型,像这里,最好使用弱引用类型,因为该实例不会一直存在,只是用完了就释放掉了.
    */
    lazy var reports:() -> String = {
        [weak self] in
        if self!.temperature != nil{
            return "\(self!.location)的天气预报是:\(self!.weather),气温是:\(self!.temperature)"
        }else{
            return "\(self!.location)的天气预报是:\(self!.weather)"
        }
    }
    
    init(location: String, weather: String){
        self.location = location
        self.weather = weather
    }
    
    //如果该实例可以被 ARC 回收,则必然调用,否则说明该实例依然存在.
    deinit{
        print("\(location)的天气预报实例被销毁了!")
    }
}

打印如下:
       成都的天气预报是:多云
       成都的天气预报实例被销毁了!

/*
当我们在闭包体内添加[unowned self] in之后,可以看到打印如下:

成都的天气预报是:多云
成都的天气预报实例被销毁了! 

调用了类的deinit方法,[unowned self]该语句就是捕获列表,其主要用途就是打破强引用关系,直接告诉Swift说这个闭包是普通的闭包,闭包中的self是对实例的无主引用,不会通过 ARC 保存这个实例.

注意捕获列表的几种格式:
普通的闭包前没有捕获列表都是强引用关系.
lazy var comeClosure(参数列表) -> 返回类型{

 //闭包题
}

标准的闭包体加捕获列表
lazy var someClosureWithCaptureList(参数列表) -> 返回类型{

  [捕获列表类型  捕获对象](参数) -> 返回类型 in
  //闭包体
}

注意:

捕获列表类型  可以是weak,也可以是 unowned
捕获对象      代表具体的类实例,如 self或者otherInstance,如果有多个实例需要慎重引用类型,中间使用逗号隔开如:[unowned self,weak otherInstance].

如果闭包本身没有参数列表,简化如下:
lazy var someClosureWithCaptureList(参数列表) -> 返回类型{

[捕获列表类型  捕获对象] in
//闭包体

}
*/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值