Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存。通常情况下,Swift 的内存管理机制会一直起着作用,你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。
自动引用计数的工作机制
当你每次创建一个类的新的实例的时候,ARC 会分配一大块内存用来储存实例的信息。内存中会包含实例的类型信息,以及这个实例所有相关属性的值。
此外,当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。
然而,当 ARC 收回和释放了正在被使用中的实例,该实例的属性和方法将不能再被访问和调用。实际上,如果你试图访问这个实例,你的应用程序很可能会崩溃。
为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。
为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢的保持住,只要强引用还在,实例是不允许被销毁的。
自动引用计数主要有两大贡献如下:
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
//闭包体
}
*/