// ARC
//这一章我写的不好,需要就去看原书
//“Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存”
//“通常情况下,Swift 内存管理机制会一直起作用,你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。”
//“然而在少数情况下,为了能帮助你管理内存,ARC 需要更多的,代码之间关系的信息。本章描述了这些情况,并且为你示范怎样才能使 ARC 来管理你的应用程序的所有内存。在 Swift 使用 ARC 与在 Obejctive-C 中使用 ARC 非常类似”
//“注意 引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。”
//1. 自动引用计数的工作机制
//“ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。”
//“当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间”
//“当 ARC 收回和释放了正在被使用中的实例,该实例的属性和方法将不能再被访问和调用。实际上,如果你试图访问这个实例,你的应用程序很可能会崩溃”
//“为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例”
//“为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的”
//2.自动引用计数实践
class Person{
let name:String
var apartment : Apartment?
init(name:String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var reference1:Person?
var reference2:Person?
var reference3:Person?
reference1 = Person(name:"john titol")
//打印 john titol is being initialized
reference2 = reference1
reference3 = reference1
//“现在这一个Person实例已经有三个强引用了”
reference1 = nil
reference2 = nil
//“只留下一个强引用,Person实例不会被销毁”
reference3 = nil
//打印 john titol is being deinitialized
//3.类实例之间的循环强引用
//“我们可能会写出一个类实例的强引用数永远不能变成0的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的循环强引用。”
//“你可以通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题”
class Apartment{
let unit:String
init(unit:String) {
self.unit = unit
}
var tenant:Person?
deinit {
print("Apartment \(unit) is being deinitialized")
}
}
var john:Person?
var unit4A:Apartment?
john = Person(name:"John applesseed")
unit4A = Apartment(unit:"4A")
john!.apartment = unit4A
unit4A!.tenant = John
//“这两个实例关联后会产生一个循环强引用。Person实例现在有了一个指向Apartment实例的强引用,而Apartment实例也有了一个指”“向Person实例的强引用。因此,当你断开john和unit4A变量所持有的强引用时,引用计数并不会降为0,实例也不会被 ARC 销毁:”
john = nil
unit4A = nil
//“当你把这两个变量设为nil时,没有任何一个析构函数被调用。循环强引用会一直阻止Person和Apartment类实例的销毁,这就在你的应用程序中造成了内存泄漏。”
//4. 解决实例之间的循环强引用
//“Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)”
//“弱引用和无主引用允许循环引用中的一个实例引用而另外一个实例不保持强引用。这样实例能够互相引用而不产生循环强引用。”
//“当其他的实例有更短的生命周期时,使用弱引用,也就是说,当其他实例析构在先时。“当其他实例有相同的或者更长生命周期时,请使用无主引用.在上面公寓的例子中,很显然一个公寓在它的生命周期内会在某个时间段没有它的主人,所以一个弱引用就加在公寓类里面,避免循环引用。
//4.1 弱引用
// “弱引用不会对其引用的实例保持强引用,因而不会阻止 ARC 销毁被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上weak关键字表明这是一个弱引用。”
//“因为弱引用不会保持所引用的实例,即使引用存在,实例也有可能被销毁。因此,ARC 会在引用的实例被销毁后自动将其赋值为nil。并且因为弱引用可以允许它们的值在运行时被赋值为nil,所以它们会被定义为可选类型变量,而不是常量。”
//“你可以像其他可选值一样,检查弱引用的值是否存在,你将永远不会访问已销毁的实例的引用”
//“注意 当 ARC 设置弱引用为nil时,属性观察不会被触发。”
//“注意 在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。但是在 ARC 中,一旦值的最后一个强引用被移除,就会被立即销毁,这导致弱引用并不适合上面的用途”
//4.2 无主引用
//“和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用在其他实例有相同或者更长的生命周期时使用。你可以在声明属性或者变量时,在前面加上关键字unowned表示这是一个无主引用”
//“无主引用通常都被期望拥有值。不过 ARC 无法在实例被销毁后将无主引用设为nil,因为非可选类型的变量不允许被赋值为nil。”
//“重要 使用无主引用,你必须确保引用始终指向一个未销毁的实例。如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。”
class Customer{
let name:String
var card:CreditCard?
init(name:String) {
self.name = name
}
deinit {
print("\(name) is being deinitializd")
}
}
class CreditCard{
let number:UInt64
unowned let customer:Customer
init(number:UInt64,customer:Customer) {
self.number = number
self.customer = customer
}
deinit {
print("card # \(self.number) is being deinitializd")
}
}
//“Customer和CreditCard之间的关系与前面弱引用例子中Apartment和Person的关系略微不同。在这个数据模型中,一个客户可能有或者没有信用卡,但是一张信用卡总是关联着一个客户。为了表示这种关系,Customer类有一个可选类型的card属性,但是CreditCard类有一个非可选类型的customer属性。”
var jack:Customer?
jack = Customer(name:"jack caption")
jack!.card = CreditCard(number:1234_5678_9999,customer:jack!)
jack = nil
//打印 jack caption is being deinitializd
// card # 123456789999 is being deinitializd
//“你可以通过unowned(unsafe)来声明不安全无主引用。如果你试图在实例被销毁后,访问该实例的不安全无主引用,你的程序会尝试访问该实例之前所在的内存地址,这是一个不安全的操作”
//个人看法:下边这个有点麻烦,搞不懂了可以去看书。
//4.3 无主引用以及隐式解析可选属性
//“上面弱引用和无主引用的例子涵盖了两种常用的需要打破循环强引用的场景。
//Person和Apartment的例子展示了两个属性的值都允许为nil,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决。”
//“Customer和CreditCard的例子展示了一个属性的值允许为nil,而另一个属性的值不允许为nil,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决。”
//“存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为nil。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解析可选属性”
//5. 闭包引起的循环强引用
//“循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如self.someProperty,或者闭包中调用了实例的某个方法,例如self.someMethod()。这两种情况都导致了闭包“捕获”self,从而产生了循环强引用。”
//“循环强引用的产生,是因为闭包和类相似,都是引用类型。当你把一个闭包赋值给某个属性时,你是将这个闭包的引用赋值给了属性。实质上,这跟之前的问题是一样的——两个强引用让彼此一直有效。但是,和两个类实例不同,这次一个是类实例,另一个是闭包。”
//“Swift 提供了一种优雅的方法来解决这个问题,称之为闭包捕获列表(closure capture list)”
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")
}
}
let heading = HtmlElement(name:"h1")
let defaultText = "some default text"
heading.asHTML = {
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
print(heading.asHTML())
//打印 <h1>some default text </h1>
//“注意 asHTML声明为lazy属性,因为只有当元素确实需要被处理为 HTML 输出的字符串时,才需要使用asHTML。也就是说,在默认的闭包中可以使用self,因为只有当初始化完成以及self确实存在后,才能访问lazy属性。”
var parragraph:HtmlElement? = HtmlElement(name:"p",text:"hello world")
print(parragraph!.asHTML())
//“上面写的HTMLElement类产生了类实例和作为asHTML默认值的闭包之间的循环强引用。”
//“实例的asHTML属性持有闭包的强引用。但是,闭包在其闭包体内使用了self(引用了self.name和self.text),因此闭包捕获了self,这意味着闭包又反过来持有了HTMLElement实例的强引用。这样两个对象就产生了循环强引用。”
//“注意 虽然闭包多次使用了self,它只捕获HTMLElement实例的一个强引用。”
parragraph = nil
//析构函数没有打印,实例未被销毁。
//5. 解决闭包引起的循环强引用
//“在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则”
//“注意 Swift 有如下要求:只要在闭包内使用self的成员,就要用self.someProperty或者self.someMethod()(而不只是someProperty或someMethod())。这提醒你可能会一不小心就捕获了self。”
//5.1 定义捕获列表
class SomeClass{
var delegate : HtmlElement?
lazy var someClosure:(Int,String)->String = {
[unowned self,weak delegate = self.delegate!](index:Int,stringToProcess:String)->String in
// 这里是闭包的函数体。
return "\(delegate?.name)"
}
}
//“在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用。
//相反的,在被捕获的引用可能会变为nil时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为nil。这使我们可以在闭包体内检查它们是否存在。”
//“注意 如果被捕获的引用绝对不会变为nil,应该用无主引用,而不是弱引用。”
class TestClass {
let name : String
let text : String?
lazy var testClosure : ()->String = {
[unowned self] in
if let text = self.text {
return "\(text)"
}else{
return "\(self.name)"
}
}
init(name:String,text:String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
var testClass : TestClass? = TestClass(name:"p",text:"hello world")
print(testClass!.testClosure())
//打印 hello world
testClass = nil
// 打印 p is being deinitialized