ARC:"Automatic Reference Counting",自动引用计数。Swift语言延续了OC的作法,也是利用ARC机制进行内存管理,和OC的ARC同样,当一些类的实例不在须要的时候,ARC会释放它们的内存。可是,在少数状况下,ARC须要知道你的代码之间的关系才能更好的为你管理内存,和OC同样,Swift中的ARC也存在循环引用致使内存泄露的状况。html
每当咱们建立一个类的新的实例的时候,ARC会从堆中分配一块内存用来存储有关该实例的信息。这块内存将持有这个实例的类型信息以及和它关联的属性的值。另外,当这个实例再也不被须要的时候,ARC将回收这个实例所占有的内存而且将这部份内存给其余须要的实例用。这样就能保证再也不被须要的实例不占用多余的内存。 可是,若是ARC释放了正在使用的实例,那么该实例的属性将不能被访问,方法将不能被调用,若是你访问它的属性或者调用它的方法时,应用会崩溃,由于你访问了一个野指针。 为了解决上述问题,ARC会跟踪每一个类的实例正在被多少个属性、常量或者变量引用,每当你将类实例赋值给属性,常量或者变量的时候它就会被"强"引用一次,当它的引用计数为0时,代表它再也不被须要,ARC就会销毁它。 下面举个例子介绍ARC是如何工做的bash
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
复制代码
上述代码建立了一个名为Person
的类,该类声明了一个非可选的类型的name
常量,一个给name
赋值的初始化方法,而且打印了一句话,用来标注初始化成功,同时声明了一个析构函数,打印了一句标志此实例被销毁的信息。闭包
var reference1: Person?
var reference2: Person?
var reference3: Person?
复制代码
上述代码声明了三个Person?
类型的变量,这三个变量为可选类型,因此被自动初始化为nil
,此时三个实例都没有指向任何一个Person
类的实例。app
reference1 = Person(name: "John Appleseed")
// Prints "John Appleseed is being initialized"
复制代码
如今建立一个Person
类的实例,而且赋值给reference1
,此时控制台会打印"John Appleseed is being initialized"
。函数
reference2 = reference1
reference3 = reference1
复制代码
而后将该实例赋值给reference2
和reference3
。如今该实例被三个"强"类型的指针引用。ui
reference1 = nil
reference2 = nil
复制代码
如上所示,当咱们将其中两个引用赋值给nil
的时候,这两个"强"引用被打破,可是这个Person
的实例并无被释放(释放信息未打印),由于还存在一个对这个实例的强引用。spa
reference3 = nil
// Prints "John Appleseed is being deinitialized"
复制代码
当咱们将第三个"强"引用打破的时候(赋值为nil
),能够看到控制台打印的"John Appleseed is being deinitialized"
析构信息。3d
上述的例子中,ARC能够很好的获取一个实例的引用计数,而且当它的引用计数为0的时候释放它。可是在实际的开发过程当中,会存在一些特殊状况,使ARC没办法获得引用计数为0这个关键点,就会形成这个实例的内存一直不被释放,两个类的实例相互"强"引用就会形成这种状况,就是"循环引用"。 苹果官方提供了两种方法来解决两个实例之间的循环引用,unowned
引用和weak
引用。指针
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
类。每个Person
的实例都有一个name
的属性和一个apartment
的可选属性,初始化为nil
,由于并非每个人都拥有一个公寓,因此是可选属性。一样的,每个Apartment
实例都有一个unit
属性和一个tenant
的可选属性,初始化为nil
,同理,不是每个公寓都有人租。同时,两个类都定义了deinit
方法,而且打印一段信息,用来让咱们清楚这个实例什么时候被销毁。code
var john: Person?
var unit4A: Apartment?
复制代码
分别定义一个Person
类型和Apartment
的变量,定义为optional
(可选类型),初始化为nil
。
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
复制代码
而后分别建立一个Person
类的实例和Apartment
类的实例,而且分别赋值给上面的定义的变量。
john
将拥有一座公寓
unit4A
,公寓
unit4A
将被
john
承租。
john!.apartment = unit4A
unit4A!.tenant = john
复制代码
由于能够肯定两个变量都被赋值为相应类型的实例,因此此处用!
对可选属性强解包。 此时,两个变量和实例以及两个实例之间的"强"引用关系以下图。
john = nil
unit4A = nil
复制代码
当咱们将两个变量设置为nil
,切断他们与实例之间的"强"引用关系,此时两个实例之间的"强"引用关系为:
Swift提供了两种办法解决类实例之间的循环引用。weak
引用和unowned
引用。这两种方法均可以使一个实例引用另外一个实例的时候,不用保持"强"引用。weak
通常应用于其中一个实例具备更短的生命周期,或者能够随时设置为nil
的状况下;unowned
用于两个实例具备差很少长的生命周期,或者说两个实例都不能被设置为nil
。
weak
引用对所引用的实例不会保持"强"引用的关系。假如一个实例同时被若干个"强引用"和一个weak
引用引用时,当全部其余的"强"引用都被打破时该实例就会被ARC释放,而且ARC会自动将这个weak
引用置为nil
。所以,weak
引用通常被声明为var
,由于它会被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") }
}
复制代码
如今,咱们将Apartment
类中的tenant
变量声明为weak
引用(在var
关键字前加weak
关键字),代表某公寓的承租人并不必定一直都是同一我的。
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
复制代码
而后和上文同样,将两个变量和实例关联。此时,它们之间的引用关系以下图。
Person
实例仍然"强"引用
Apartment
实例,可是
Apartment
实例'weak'引用
Person
实例。
john
和
unit4A
两个变量仍然"强"引用两个实例。当咱们把
john
变量对
Person
实例的"强"引用打破的时候,即将
john
设置为
nil
,就没有其余的"强"引用引用
Person
实例,此时,
Person
实例被ARC释放,同时
Apartment
实例的
tenant
变量被设置为
nil
。
john = nil
// Prints "John Appleseed is being deinitialized"
复制代码
unit4A
设为
nil
,能够看到
Apartment
实例也被销毁。
unit4A = nil
// Prints "Apartment 4A is being deinitialized"
复制代码
和weak
引用同样,unowned
引用也不会保持它和它所引用实例之间的"强"引用关系,而是保持一种非拥有(或未知)的关系,使用的时候也是用unowned
关键字修饰声明的变量。不一样的是,两个互相引用的对象具备差很少长的生命周期,而不是其中一个能够提早被释放(weak
),有点同甘共苦的意思。 Swift要求unowned
修饰的变量必须一直指向一个实例,而不是有些时候为nil
,所以,ARC也不会将这个变量设置为nil
,因此咱们通常将这个引用声明为非可选类型。PS:请确保你声明的变量一直指向一个实例,若是这个实例被释放了,而unowned
变量还在引用它的话,你会获得一个运行时错误,由于,这个变量是非可选类型的。
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") }
}
复制代码
上面这个例子定义了两个类:Customer
和CreditCard
,每一个顾客均可能会有一张信用卡(可选类型),每一个信用卡都必定会有一个持有他们的顾客(非可选类型,卡片为顾客定制)。所以,Customer
类有一个CreditCard?
类型的属性,CreditCard
类也有一个Customer
类型的属性,而且被声明为unowned
,以此来打破循环引用。每张信用卡初始化的时候都须要一名持有它的顾客,由于信用卡自己就是为顾客定制的。
var john: Customer?
复制代码
而后声明一个Customer?
类型的变量john
,初始化为nil
。接着建立一个Customer
的实例,而且将它赋值给john
(让john
引用它、指向它都是一个意思)。
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
复制代码
(第一句代码赋值以后,咱们知道john
确定不是nil
,因此用!
解包不会有问题) 而后,两个实例之间的引用关系为:
Customer
实例"强"引用
CreditCard
实例,
CreditCard
实例'unowned'引用
Customer
实例,接着,咱们将
john
对
Customer
实例的"强"引用打破,即将
john
设置为
nil
。
john = nil
// Prints "John Appleseed is being deinitialized"
// Prints "Card #1234567890123456 is being deinitialized"
复制代码
Customer
实例和
CreditCard
实例都被销毁了。
john
被设置为
nil
以后,就没有"强"引用引用
Customer
实例,因此,
Customer
实例被释放,也就没有"强"引用引用
CreditCard
实例,所以
CreditCard
实例也被释放。
以上例子证实,两种方式均可以解决循环引用的问题,可是要注意它们使用的范围。weak
修饰的变量能够被设置为nil
(引用的实例的生命周期短于另外一个实例),unowned
修饰的变量必需要指向一个实例(形成循环引用的两实例的生命周期差很少长,不会出现一方被提早释放的状况),一旦它被释放了,就千万别再使用了。
Swift中的闭包是一种独立的函数代码块,它能够像一个类的实例同样在代码中赋值、调用和传递,也能够被认为某个匿名函数的实例,其实就是OC中的block。它和类同样也是引用类型的,因此它的函数体中使用的引用都是"强"引用。
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> 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
变量,因此闭包被HTMLElement
实例"强"引用,而闭包又捕获(关于闭包捕获变量,参考官方文档Capturing Values)了HTMLElement
的实例中的text
和name
属性,所以它又"强"引用HTMLElement
实例,这样就形成了循环引用,由于text
属性可能为空,因此定义为可选属性。
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"
复制代码
咱们建立一个HTMLElement
实例,并将它赋值给paragraph
变量,而后访问它的asHTML
属性。此时的内存示例为下图,能够看到HTMLElement
实例和闭包之间的循环引用。
paragraph
设置为
nil
时,控制台并无打印任何销毁信息,由于循环引用。
经过上文(三)的分析,咱们知道unowned
引用对实例的非拥有关系,所以,咱们能够经过以下方式解决循环引用:
lazy var asHTML: () -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
复制代码
[unowned self] in
,这段代码,表明闭包中的self
指针都被unowned
修饰。这样就可使闭包对实例的"强"引用变成'unowned'引用,从而打破循环引用。 当HTML的element为标题的时候,此时若是text
属性为空,咱们想返回一个默认的text做为标题,而不是只有<h/>
这种标签。
let heading = HTMLElement(name: "h1")
let defaultText = "some default text"
heading.asHTML = {
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
print(heading.asHTML())
// Prints "<h1>some default text</h1>"
复制代码
这段代码也会形成HTMLElement
对其自身的循环引用。咱们仍然可使用unowned
关键字打破循环引用:
heading.asHTML = {
[unowned heading] in
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
// Prints "<h1>some default text</h1>"
// Prints "h1 is being deinitialized"
复制代码
unowned
会使闭包中对heading
的"强"都改成'unowned'引用。 或者,可使用weak
属性打破循环引用:
weak var weakHeading = heading
heading.asHTML = {
return "<\(weakHeading!.name)>\(weakHeading!.text ?? defaultText)</\(weakHeading!.name)>"
}
// Prints "<h1>some default text</h1>"
//Prints "h1 is being deinitialized"
复制代码
上文(三)中可知,weak
修饰的变量为可选类型,并且,咱们对变量进行了一次赋值,就能够确保weakHeading
指向heading
引用的实例,因此能够放心的使用!
对它解包。 上面这段代码一样可使闭包对HTMLElement
实例的"强"引用变为weak
引用,从而打破循环引用。 (ARC会自动回收不被使用的对象,因此不用手动将变量设置为nil
)