您的位置:首页 > 移动开发 > Swift

Swift 自动引用计数(ARC)

2016-01-29 14:06 381 查看
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
//闭包体

}
*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: