Swift管理内存方式和OC极其相似,都是采用自动引用计数 ARC
机制来跟踪和管理。
ACR
会在实例不再使用的情况下自己释放其占用的内存空间,所以通常情况下不需要我们自己来手动释放。
引用计数应用于类的实例
(注意:结构体
和枚举
是值类型So结构体和枚举不是通用引用计数来管理内存的。)
本章结合了Swift 官方文档。挑一些主要点进行描述Swift的引用计数机制
章节如下:
- 自动引用计数工作机制
- 自动引用计数实践
- 类实例之间的循环强引用
- 解决实例之间的循环强引用
- 闭包(即OC-Block)引起的循环强引用
- 解决闭包引起的循环强引用
1.自动引用计数工作机制
简单说就是每当我们创建一个实例的时候ARC
都会开辟一块内存空间.供这个实例使用,内存中会包含这个实例及这个实例的类型信息。无论这个实例是被赋值为属性,变量或常量
。直到我们不再使用这个实例,ARC
会自动释放此实例,并将这块内存空间挪它使用。其机制和OC
一样,若访问被释放的实例则Crash。只要这个实例仍存在,强引用就伴随其一直存在,就可访问。
2.自动引用计数实践
若上面这段富有理论性的话大家不太明白,请注意看这个栗子
class Person {
let name: String
init(name: String) {//init 构造函数。"人"这个实例对象,具有一个自己的属性"名字",人被初始化了,其名字也同时被初始化。
self.name = name
print("\(name) is being initialized")
}
deinit {//人挂了,其属性肯定也挂了(”deinit析构函数“ 等同OC dealloc )
print("\(name) is being deinitialized")
}
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
/*我们有三个为Person类型的变量. 注意问号"?". 代表可选类型,也就是,可为空。
所以,这时候还没有强引用,这三个变量的初始值都为nil.还没有被创建.*/
/*
接下来我们创建Person类实例对象,给其中一个变量赋值.注意:这时候reference1到Person类实例之间建立强引用,所以ARC保证Person一直存在内存中。可以看到打印。
*/
reference1 = Person(name: "John Appleseed")
// 打印 "John Appleseed is being initialized
/*
此时,我们将Person也赋值给其它两个变量,这时候Person强引用加二
*/
reference2 = reference1
reference3 = reference1
/*
此时我们再将两个变量赋值为nil.则Person就少去两个强引用。注意:此时Person还是存在的哈,因为还存在一个强引用,还有一个变量是它。
*/
reference1 = nil
reference2 = nil
/*
好了,我们可手动销毁最后一个强引用。这时候ARC会释放掉Person,可看打印。
*/
reference3 = nil
// 打印 “John Appleseed is being deinitialized
3.类实例之间的循环强引用
栗子
有两个类,Person
Apartment
.
Person
具有实例属性为apartment
,apartment
是可选的,所以初始值为nil
,所以这时候Preson
并不会产生强引用加一。这块儿注意,这是一会演示产生循环强引用的关键.
Apartment
.同Person
一样也有个初始化为nil
的实例属性Person
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
现在我在另一个类中写两个实例属性,分别为Person
和Apartment
的初始值为nil
的可选属性john,unit4
.
var john: Person?
var unit4A: Apartment?
现在创建特定的Person
和Apartment
实例,并将其赋值给john和unit4A变量
注意了:这时候john
和Person
之间建立了强引用.unit4A
和Apartment
之间也建立了强引用.
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
现在我将unit4A
赋值给John
的apartment
,将john
赋值给unit4A
的tenant
注意了:关键就在这儿了,这时候john
和 unit4A
的内部实例属性是有强引用的.所以在下面我将john
和unit4A
,手动制空,其会被ARC释放吗?析构函数deinit
会被调用吗? 答案是肯定不会,这就造成了类实例之间的循环强引用.
为什么?虽然我将john
和john
手动制空,释放了一个强引用,但是其内部是不是还有个实力属性拥有强引用哇?。这就造成了内存泄漏,不得了了。
john!.apartment = unit4A
unit4A!.tenant = john
john = nil
unit4A = nil
那怎么解决呢? 请往下看
4.解决实例之间的循环强引用
Swift 提供了两种方法来解决使用类的属性时,所遇到的循环强引用问题.
弱引用weak reference, 无主引用unowned reference
当其他的实例有更短的生命周期使用弱引用,反之使用无主引用.
- ****弱引用****
关键字weak
当其他的实例有更短的生命周期时,使用弱引用,也就是说,当其他实例析构在先时。
修饰可为nil
的,可选类型变量。
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
john = nil
// 打印 “John Appleseed is being deinitialized
unit4A = nil
// 打印 “Apartment 4A is being deinitialized
看懂没? 没错,就是
weak
关键字修饰Apartment
类的实例属性Person
ARC
会在实例的引用被销毁的时候自动将其被weak
修饰的实例属性设置为nil
,所以当john
挂了,它也就彻底挂了,弱引用不会对其引用的实例进行强引用!注意 当
ARC
设置弱引用没nil
时,它的属性观察器是不会被触发的.PS:
Apartment
和Person
的例子很好的演示了,两个类都拥有一个可为nil的可选属性。并且在另一个类中,这两个类的实例属性相互持有。这就产生的潜在的循环强引用,这时候我们用弱引用weak
再适合不过了.
- ****无主引用****
在属性或变量前面加一个关键字unowned
表示这是一个无主引用。
无主引用和弱引用一样不会牢牢保持一个实例的引用。
不同就是ARC不会在实例销毁后将其设置为nil。因为其是非可选 的类型的变量,所以不允许被设置为nil
。
直接看栗子
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: John!)
john = nil
// 打印 “John Appleseed is being deinitialized”
// 打印 ”Card #1234567890123456 is being deinitialized
两个类,Customer
信用卡客户,和CreditCard
信用卡
信用卡客户:有个名字是肯定的let name: String
,信用卡不一定有var card: CreditCard?
信用卡:卡号和卡持有者,是肯定有的,所以都用let
修饰.注意正是因为这个用无主引用关键字unowned
修饰的实例对象Customer
卡片持有者,所以之后相互引用才不会导致循环强引用.然后我们必须通过卡号和持卡者创建信用卡.
看图示
Customer
,和CreditCard
,Customer
一个属性值可为nil,一个属性值不可为nil,然后在里一个类中,这两个类相互引用,产生强引用。这种情况无主引用最合适不过了。
- ****无主引用以及隐式解析可选属性****
栗子
class Country {
let name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// 打印 “Canada's capital city is called Ottawa
感叹号(City!)的方式,将Country的capitalCity属性声明为隐式解析可选类型的属性。这意味着像其他可选类型一样,capitalCity属性的默认值为nil,但是不需要展开它的值就能访问它。
capitalCity属性在初始化完成后,能像非可选值一样使用和存取,同时还避免了循环强引用。
5. 闭包引起的循环引用
闭包中会如何产生循环强引用?
闭包中引用了self
,比如self.someProperty
,self.someMethod
为什么?
这是因为闭包和类相似都是属于引用类型
。当我们把闭包赋值给类的某个属性,其实是把这个引用赋值给这个属性。和上面类实例之前的循环强引用一样,会产生两个强引用一直存在。是不是不太好理解?来我们看栗子
在上图例子相信大家就会明白,什么是闭包引起的循环强引用,以及怎样解决闭包引起的循环强引用。
有的同学可能不太理解,我靠,这是什么写法?这是Swift的捕获列表。
捕获列表:
捕获列表中的每一项都由一对元素组成,一个元素是weak或unowned关键字,另一个元素是类实例的引用(例如self)或初始化过的变量(如delegate = self.delegate!)
****总结:****
ARC自动帮我们管理内存,也是采用垃圾回收机制。
切记:
两个类的实例相互保持对方的强引用时会产生循环强引用。闭包会产生循环强引用,尤其Rx
,RAC
的回调实现中很敏感。
开发中通常我们不会手动将实例对象制空。若制空,再访问那么,你懂得。
主要说下循环强引用:
循环强引用实质上说白了就是,这个类最终没有释放最终没走deinit方法。为什么?有可能是你的类与实力之间产生的循环强引用,有可能是你的闭包引起的循环强引用。
怎么解决?大家可以参考上面的例子。在实际开发中呢,我们最常见的就是闭包引起的循环引用。这里总结下解决办法
//捕获列表:
object.block = { [weak self] in // 弱引用
self.xxx
}
object.block = { [unowned self] in //无主引用
self.xxx
}
//若引用修饰self:
weak var ws = self
object.block = { [unowned self] in
ws.xxx
}
weak和unowned区别:
unowned 更像OC的 unsafe_unretained ,而 weak 就是以前的 weak 。
unowned设置以后即使它原来引用的内容已经被释放了,它仍然会保持对被已经释放了的对象的一个 "无效的" 引用,它不能是 Optional 值,也不会被指向 nil 。如果你尝试调用这个引用的方法或者访问成员属性的话,程序就会崩溃。
weak 在引用的内容被释放后,标记为 weak 的成员将会自动地变成 nil (所以被标记为 weak 的变量一定需要是 Optional 值)。
关于两者使用的选择,存在被释放的可能,那就选择用 weak 。开发中用weak 会多一些.
题外话:最近忙了一阵儿,回头续写,写了一半的文章,发现太不干了。所以以后的文章会尽量写一写干的东西。写这么大一篇文章,我觉得直接看总结这部分就好了。😭