swift 学习- 18 -- 自动引用计数

// Swift 使用 自动引用计数 (ARC) 机制来跟踪和管理你的应用程序的内存, 通常情况下, Swift 内存管理机制会一直起作用, 你无须自己来考虑内存的管理, ARC 会在类的实例不再被使用时,自动释放其占用的内存

// 然而在少数情况下, 为了能帮助你管理内存, ARC 需要更多的能带吗之间关系的信息,

// 注意 : 引用计数仅仅应用于 类的实例, 结构体 和 枚举类型是值类型, 不是引用类型, 也不是通过引用的方式存储和传递

// 自动引用计数的工作机制

// 当你每次创建一个类的新的实例的时候, ARC 会分配一块内存来存储该实例的信息, 内存中会包含实例的类型信息, 以及这个实例所有相关的存储型属性的值

// 此外,当实例不再被使用时, ARC 释放实例所占用的内存, 并让释放的内存能挪作他用, 这确保不再使用的实例, 不会一直占用内存空间

// 然而,当 ARC 收回和释放了正在使用的实例,该实例的属性和方法将不能再被访问和调用, 实际上, 如果你试图访问这个实例, 你的应用程序可能会崩溃

// 为了确保使用中的实例不会被销毁, ARC 会跟踪和计算每一个实例正在被 多少属性, 常量 和 变量 所引用, 哪怕实例的引用数为 1, ARC 都不会销毁这个实例

// 为了是上述称为可能, 无论你将实例赋值给属性, 常量 或 变量, 他们都会创建次实例的强引用, 之所以称之为 强引用 , 是因为他会将实例 牢牢保持住, 只要 强引用在, 实例是 不允许被销毁的

// 自动引用计数实践

// 下面的例子展示了自动引用计数的工作机制, 例子以一个简单的 Person 类开始, 并定义了一个叫做 name 的常量属性

class Person{

let name: String

init(name: String) {

self.name = name

print("\(name) is being initialized")

}

deinit {

print("\(name) is being deinitialized")

}

}

// Person类有一个构造函数,此构造函数为实例的name属性赋值,并打印一条消息以表明初始化过程生效。Person类也拥有一个析构函数,这个析构函数会在实例被销毁时打印一条消息。

var refreence1: Person?

var refreence2: Person?

var refreence3: Person?

// 由于这些变量被定义为可选类型, 他们的值会被自动初始化为 nil ,目前还不会引用到 Person 类的实例

refreence1 = Person.init(name: "Join Appleseed")

// 由于 Person 类的新实例被赋值给了 refreence1 变量. 所以 refreence1 到 Person 类新实例之间建立了一个 强引用, 正是因为这个强引用, ARC 会保证 Person 实例被保持在内存中不被销毁

refreence2 = refreence1

refreence3 = refreence1

// 现在这一个 Person 实例已经有三个 强引用了

// 如果你通过给其中两个变量赋值nil的方式断开两个强引用(包括最先的那个强引用),只留下一个强引用,Person实例不会被销毁:

refreence1 = nil

refreence2 = nil

// 在你清楚地表明不再使用这个Person实例时,即第三个也就是最后一个强引用被断开时,ARC 会销毁它:

refreence3 = nil

// 类实例之间循环 强引用

// ARC 会跟踪你所 创建的 Person 实例的引用数量, 并且会在 Person 实例不再被需要时销毁它

// 然而, 我们可能会写出一个类实例的 强引用数 永远不能为 0 的代码, 如果两个类实例互相持有对方的 强引用, 因而每个实例都让对方一直存在, 就是这种情况, 就是所谓的 循环强引用

// 你可以通过定义类之间的关系 为弱引用 或无主引用, 以替代 强引用, 从而解决循环强引用的问题, 不管怎样, 在学习怎样 解决循环强引用之前, 很有必要知道它是怎样产生的

// 下面例子中两个类 : Person2 和 Apartment ,用来建模公寓 和 它其中的居民

class Person2{

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: Person2?

deinit {

print("Apartment \(unit) is being deinitialized")

}

}

// 每一个 Person 实例有一个类型为 String ,名字为 name 的属性, 并有一个可选的初始化 为 nil 的apartment 属性, aoartment 属性是可选的, 因为一个人并不是拥有公寓

// 类似的, 每个 Apartment 实例有一个叫做 unit,类型为 String 的属性, 并有一个可选的初始化为 nil 的 tenmant 属性, tenant 属性是可选的, 因为一栋公寓并不是总有有居民

// 这两个类都定义了 析构函数, 用以在类型被析构的时候输出信息, 这能让你知道 Person2 和 Apartment 的实例是否像预期的那样被销毁

var john: Person2?

var unit4A: Apartment?

// 这两个变量都被初始化为 nil, 这正是可选类型的优点

john = Person2.init(name: "John")

unit4A = Apartment.init(unit: "4A")

// 现在你能够将这两个实例关联在一起,这样人就能有公寓住了,而公寓也有了房客

john!.apartment = unit4A

unit4A!.tenant = john

// 注意 : 感叹号是用来展开和访问可选变量 john 和 unit4A 中的实例, 这样实例的属性才能赋值

// 不幸的是, 这两个实例关联之后会产生一个循环强引用, Person2 实例现在有了一个指向 Apartment 实例的强引用, 而 Apartment 实例也有了一个指向 Person2 实例的强引用, 因此, 当你断开 john 和 unit4A 变量所持有的强引用时, 引用计数并不会降为 0, 实例也不会被 ARC 销毁

john = nil

unit4A = nil

// 注意 : 当你那这两个变量 设为 nil 时, 没有任何一个析构函数被调用, 循环强引用会一直阻止 Person2 , Apartment 实例的销毁, 这就使你的程序中造成了 内存泄漏

// 解决实例之间的循环强引用

// Swift 提供了两种方法来解决你在使用类的属性时所遇到的循环强引用问题, 弱引用 和 无主引用

// 弱引用 和 无主引用 允许循环引用中的 一个实例引用 而另外一个实例 不保持强引用, 这样实例能够互相引用而不产生强引用

// 当其他的实例有更短的生命周期时, 使用弱引用, 也就是说, 当其他实例析构在先是, 在上面公寓的乐例子中, 很显然一个公寓在它的生命周期内会在某个时间段没有他的主人, 所以一个弱引用就加在公寓类里面, 避免循环引用, 相比之下, 当其他实例有相同的 或者更长的生命周期时, 请使用 无主引用

// 弱引用

// 弱引用 不会对其引用的实例保持 强引用, 因而不会阻止 ARC 销毁被引用的实例, 这个特发性阻止了引用变为 循环强引用, 声明属性 或者 变量时,在前面加上 weak 关键字表明这是一个 弱引用

// 因为 弱引用 不会保持所引用的实例, 即使引用存在, 实例也有可能被销毁, 因此 ARC 会在引用的实例北欧销毁后自动将其赋值为 nil, 并且因为 弱引用 可以允许他们的值在运行的时候被赋值 为 nil, 所以他们会被定义为可选型变量, 而不是常量

// 当 ARC 设置的 弱引用 为 nil 时, 属性观察不会被触发

class Apartment2{

let unit: String

init(unit: String) {

self.unit = unit

}

weak var tenant: Person2?

deinit

{

print("Apartment \(unit) is being deinitillized")

}

}

// 无主引用

// 和 弱引用 类似, 无主引用 不会牢牢保持住引用的实例, 和弱引用不同的是, 无助引用 在其他实例有相同或者 更长的声明周期的时候使用, 你可以在声明属性或者变量 时, 在前面加上关键字 unowned 表示这是一个 无主引用

// 无主类型 通常都被期望拥有值, 不过 ARC 无法在实例被销毁后将无主引用 设为 nil,因为非可选类型的变量不允许被赋值为 nil

// 重要:

// 使用无主引用, 你必须确保引用始终指向一个未销毁的实例

// 如果你试图在实例被销毁后, 访问该实例的无主引用, 会触发运行时错误

// 下面的例子定义了两个类,Customer和CreditCard,模拟了银行客户和客户的信用卡。这两个类中,每一个都将另外一个类的实例作为自身的属性。这种关系可能会造成循环强引用。

// Customer和CreditCard之间的关系与前面弱引用例子中Apartment和Person的关系略微不同。在这个数据模型中,一个客户可能有或者没有信用卡,但是一张信用卡总是关联着一个客户。为了表示这种关系,Customer类有一个可选类型的card属性,但是CreditCard类有一个非可选类型的customer属性。

// 由于信用卡总是关联着一个客户, 因此将 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

unowned let customer: Customer

init(number: UInt64, customer: Customer) {

self.number = number

self.customer = customer

}

deinit {

print("Card #\(number) is being deinitialized")

}

}

// 注意 : CreditCard 类的 number 属性定义为 UInt64 类型而不是 Int 类型, 以确保 number 属性的存储量在 32位 和 64位系统上都能足够容纳 16位的卡号

var joni: Customer?

joni = Customer.init(name: "joni")

joni!.card = CreditCard.init(number: 12345678901234, customer: joni!)

// Customer实例持有对CreditCard实例的强引用,而CreditCard实例持有对Customer实例的无主引用。

// 由于customer的无主引用,当你断开john变量持有的强引用时,再也没有指向Customer实例的强引用了:

joni = nil

// 无主引用以及隐式解析可选属性

// 上面弱引用和无主引用的例子涵盖了两种常用的需要打破循环强引用的场景。

// Person和Apartment的例子展示了两个属性的值都允许为nil,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决。

// Customer和CreditCard的例子展示了一个属性的值允许为nil,而另一个属性的值不允许为nil,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决。

// 然而,存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为nil。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解析可选属性。

// 这使两个属性在初始化完成后能被直接访问(不需要可选展开),同时避免了循环引用。这一节将为你展示如何建立这种关系。

// 下面的例子定义了两个类,Country和City,每个类将另外一个类的实例保存为属性。在这个模型中,每个国家必须有首都,每个城市必须属于一个国家。为了实现这种关系,Country类拥有一个capitalCity属性,而City类有一个country属性:

class Country{

let name: String

var capitalCity: City!

init(name: String, capitalName: String) {

self.name = name

self.capitalCity = City.init(name: capitalName, country: self)

}

}

class City{

let name: String

unowned let country: Country

init(name: String,country: Country) {

self.name = name

self.country = country

}

}

// 闭包引起的循环强引用

// 前面我们看到了循环强引用 是在两个类实例属性互相保持对方的强引用时产生的, 还知道如何用 弱引用 和 无主引用 来打破这些循环强引用

// 循环强引用还会发生在当你将一个 闭包赋值给实例的某个属性, 并且这个闭包体中 又使用了这个类实例时, 这个闭包体中可能访问实例的某个属性, 例如: self.someProperty, 或者闭包中调用了实例的某个方法 例如: self.someMethod(). 这两种情况都导致了闭包 捕获 self, 从而产生了循环引用.

// 循环强引用的产生, 是因为闭包和类相似, 都是引用类型, 当你把一个闭包赋值给某个属性时, 你是将这个闭包的引用赋值给了属性. 实质上, 这跟之前的问题是一样的 -- 两个强引用让彼此一直有效, 但是,和两个类实例不同, 这次一个是类实例, 另一个是闭包

// Swift 提供了一种优雅的方法来解决这个问题, 称之为 '闭包捕获列表',

// 解决闭包引起 的循环强引用

// 在定义闭包时 同时定义捕获列表 作为闭包的一部分, 通过这种方式可以解决 闭包和类实例之间的 循环强引用, 捕获列表定义了 闭包体内捕获一个或者多个引用类型的规则, 跟解决两个类实例之间的循环强引用一样, 声明每个捕获的引用为 弱引用 或 无主引用, 而不是强引用吗应当根据代码关系来定义使用 弱引用 还是 无主引用

// 注意: Swift 有如下要求, 只要在闭包中使用 self 的成员, 就要用 self.someProperty 或者 self.someMethod() ,这提醒你可能会一不小心就捕获了 self

// 定义捕获列表

// 捕获列表中的每一项都有 一对元素组成, 一个元素是 weak 或 unowned 关键字, 另一个元素是类实例的引用, (例如 self), 或初始化过的变量, (如 delegate = self.delegate!), 这些项在方扩内中用逗号隔开

// 如果闭包有参数列表和返回类型,把捕获列表放在它们前面:

// 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 , 应该是 无主引用, 而不是弱引用,

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")

}

}

// 这里, 捕获列表是 [unowned self] 表示,将 self 捕获为无主引用 而不是强引用