自动引用计数
与OC
相同 Swift
使用自动引用计数(ARC)来跟踪和管理应用程序内存
注意
引用计数仅仅应用于类的实例。结构体和枚举是值类型,不是引用类型,也不是通过引用的方式存储和传递的
工作机制
创建类的实例时,ARC
会分配一块内存来存储实例信息.不再使用该实例,ARC将其释放.当是实例被释放后,其方法或属性均不能被访问或调用。
因此,为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。
循环强引用
如果两个类实例互相引用,那么类实例的强引用数永远不能变为0,这就是循环强引用
解决循环强引用方法
解决办法: 弱引用 无主引用
将循环引用中一个实例对另外的实例不保持强引用
当其他的实例有更短的生命周期时,使用弱引用,即其 实例 析构在先时; 当其他实例有相同或者更长的声明周期时,请使用无主引用
弱引用
弱引用不会对其引用的实例保持强引用,因而不会阻止ARC
销毁被引用的实例。
声明属性或变量时,在前面加上weak
关键字表明是个弱引用
ARC
会在引用的实例被销毁后自动将其赋值为nil
.并且弱引用可以允许它们的值运行时赋值为nil
,所以会被定义为可选类型变量而不是常量
弱引用为可选类型变量,可以通过检查是否为nil,防止访问已经销毁的实例的引用
注意
在
ARC
设置弱引用为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
//因为Apartment 中 的 tenant 属性为弱引用,因此即使这样也不会产生循环引用
john = nil
// 打印 “John Appleseed is being deinitialized”
unit4A = nil
// 打印 “Apartment 4A is being deinitialized”
变量john
和unit4A
在被赋值为nil
后,Person
实例和Apartment
实例的析构函数都打印出“销毁”的信息
注意:
在使用垃圾收集的系统里
无主引用
在属性前加unowned
关键字进行声明无主引用
与弱引用类似,无主引用不会保持住引用的实例
与弱引用不同,无主引用用在与其他实例有相同或者更长声明周期时使用
无主引用为非可选类型,因此ARC
无法在实例被销毁后将无主引用设置为nil
注意:
使用无主引用,必须保证引用始终指向一个未销毁的实例
试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误
举个栗子:
Customer
客户和CreditCard
银行卡之间的关系,两个类都将另外一个类的实例作为自身属性,关系是客户可能没有银行卡,但是银行卡一定有客户,因此将银行卡的customer
设置为无主引用
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 //确保在32、64位机器上均可以保存16位的卡号
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
实例的强引用,而CreditCard
实例持有对 Customer
实例的无主引用
注意:
上面是展示如何使用安全的无主引用。对于需要禁用运行时的安全检查的情况(例如:处于次年功能方面原因),Swift还提供了不安全的无主引用。与所有的不安全操作类似,需要负责检查代码确保其安全性。可以通过
unowned(unsafe)
声明不安全的无主引用。如果视图在实力被销毁后,访问该实例的不安全无主引用,你的程序会尝试访问该实例之前所在的内存地址,这是一个不安全的操作
无主引用以及隐式解析可选属性
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
}
}
我们在Country
的capitalCity
属性声明为City!
隐式解析可选类型的属性,表明像其他可选类型一样,默认值为nil
,但是不需要展开他的值就能访问
因为capitalCity
的默认值为默认为nil
,因此在构造country时,当name赋值以后,初始化就完成了,此时我们就能把这个初始化的country
实例传递给City
来构造City实例
闭包引起的循环强引用
闭包和类 类似,都是引用类型,因此,将闭包赋值为某属性时,是将这个闭包的引用赋值给属性,因此当闭包体中访问实例的某个属性时,就会造成循环引用
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: Void -> 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")
}
}
asHTML
声明为lazy
属性,因为只有当元素确实需要被处理为HTML
输出的字符串时,才需要使用asHTML
。也就是说,在默认的闭包中可以使用self
,因为只有当初始化完成以及self
确实存在后,才能访问lazy
属性。
闭包捕获列表解决闭包引起的循环引用
对于一个类的闭包属性,想要改变这个属性的行为,可以通过给这个属性再赋值一个闭包
HTMLElement
有类和作为asHTML
默认值的闭包之间的循环强引用
此时HTMLElement
实例和它的闭包是不会被销毁和释放的
注意:
虽然闭包中多次使用
self
,但是它只捕获HTMLElement
实例的一个强引用
解决闭包引起的循环强引用
在定义闭包时同时定义捕获列表作为闭包的一部分,捕获列表定义了闭包体内捕获一个或多个引用类型的规则.声明捕获的引用为弱引用或者无主引用,而不是强引用
注意:
Swift
规定:在闭包中使用self
的成员,要使用self.someProperty
或者self.someMethod()
,而不是使用someProperty
或someMethod()
这是为了提醒你 一不小心就捕获了self
定义捕获列表
捕获列表由一对元素组成,一个是weak
或unowned
关键字,另一个是类实例的引用如self
或初始化的变量.
在方括号中用逗号隔开
如果闭包有参数列表和返回类型,把捕获列表放在它们前面:
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// 这里是闭包的函数体
}
/**
如果闭包没有指明参数列表或者返回类型,即它们会通过上下文推断,
那么可以把捕获列表和关键字 in 放在闭包最开始的地方
*/
lazy var someClosure: Void -> String = {
[unowned self, weak delegate = self.delegate!] in
// 这里是闭包的函数体
}
弱引用和无主引用
在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用
。(即被捕获的引用不会变为nil使用无主引用)
在被捕获的引用可能会变为nil
时,将闭包内的捕获定义为弱引用
,弱引用总是可选类型,在引用的实例被销毁后自动置为nil
注意
如果被捕获的引用绝对不会变成nil,应该用无主引用,而不是弱引用
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: Void -> String = {
[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")
}
}
在asHTML
闭包中多了一个捕获列表,[unowned self]
将self声明为无主引用
这时候没有了循环引用,我们就可以销毁HTMLElement
实例了