/*
• 自动引用计数的工作机制
• 自动引用计数实践
• 类实例之间的循环强引用
• 解决实例之间的循环强引用
• 闭包引起的循环强引用
• 解决闭包引起的循环强引用
*/
//自动引用计数器的工作机制
/*当你每次创建一个类的新的实例的时候,ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信 息,以及这个实例所有相关的存储型属性的值。
此外,当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的 实例,不会一直占用内存空间。
然而,当 ARC 收回和释放了正在被使用中的实例,该实例的属性和方法将不能再被访问和调用。实际上,如果你 试图访问这个实例,你的应用程序很可能会崩溃。
为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实 例的引用数为1,ARC都不会销毁这个实例。
为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之
为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。*/
//自动引用计数实践
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
// Person 类有一个构造函数,此构造函数为实例的 name 属性赋值,并打印一条消息以表明初始化过程生效。 on 类也拥有一个析构函数,这个析构函数会在实例被销毁时打印一条消息。
var reference1: Person?
var reference2: Person?
var reference3: Person?
reference1 = Person(name: "John Appleseed")
// 应当注意到当你调用 Person 类的构造函数的时候, “John Appleseed is being initialized” 会被打印出来。由 此可以确定构造函数被执行。
reference2 = reference1
reference3 = reference1
//如果你通过给其中两个变量赋值 nil 的方式断开两个强引用(包括最先的那个强引用),只留下一个强引用, rson 实例不会被销毁:
reference1 = nil
reference2 = nil
//在你清楚地表明不再使用这个 Person 实例时,即第三个也就是最后一个强引用被断开时,ARC 会销毁它:
reference3 = nil
// 打印 “John Appleseed is being deinitialized”
//类实例之间的循环引用
//在上面的例子中,ARC 会跟踪你所新创建的 Person 实例的引用数量,并且会在 Person 实例不再被需要时销毁它。
class Person1 {
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:Person1?
deinit {
print("Apartment \(unit) is being deinitialized")
}
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
// 不幸的是,这两个实例关联后会产生一个循环强引用。 Person 实例现在有了一个指向 Apartment 实例的强引 用,而 Apartment 实例也有了一个指向 Person 实例的强引用。因此,当你断开 john 和 unit4A 变量所持有的强 引用时,引用计数并不会降为 0 ,实例也不会被 ARC 销毁:
john = nil
unit4A = nil
//注意,当你把这两个变量设为 nil 时,没有任何一个析构函数被调用。循环强引用会一直阻止 Person 和 Apartme nt 类实例的销毁,这就在你的应用程序中造成了内存泄漏。
//解决实例之间的循环引用
//弱引用:声明属性或者变量时,在前面加上 weak 关键字表明这是一个弱引用。
// 然后跟之前一样,建立两个变量( john 和 unit4A )之间的强引用,并关联两个实例:
//无主引用
//和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用在其他实例有相同或者更长 的生命周期时使用。你可以在声明属性或者变量时,在前面加上关键字 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:NSInteger
unowned let customer: Customer
init(number:NSInteger , customer: Customer) {
self.number = number
self.customer = customer
}
deinit {
print("Card #\(number) is being deinitialized")
}
}
var john1: Customer?
john1 = Customer(name: "John Appleseed")
john1!.card = CreditCard(number: 1234_5678_9012_3456, customer: john1!)
//无主引用以及隐式解析可选属性
//两个属性都必须有值,并且初始化完成后永远不会为 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
}
}
//为了建立两个类的依赖关系, City 的构造函数接受一个 Country 实例作为参数,并且将实例保存到 country 属性。
//Country 的构造函数调用了 City 的构造函数。然而,只有 Country 的实例完全初始化后, Country 的构造函数 才能把 self 传给 City 的构造函数
//为了满足这种需求,通过在类型结尾处加上感叹号( City! )的方式,将 Country 的 capitalCity 属性声明为隐 式解析可选类型的属性。这意味着像其他可选类型一样, capitalCity 属性的默认值为 nil ,但是不需要展开它 的值就能访问它
// 由于 capitalCity 默认值为 nil ,一旦 Country 的实例在构造函数中给 name 属性赋值后,整个初始化过程就完 成了。这意味着一旦 name 属性被赋值后, Country 的构造函数就能引用并传递隐式的 self 。 Country 的构造函 数在赋值 capitalCity 时,就能将 self 作为参数传递给 City 的构造函数。
//以上的意义在于你可以通过一条语句同时创建 Country 和 City 的实例,而不产生循环强引用,并且 的属性能被直接访问,而不需要通过感叹号来展开它的可选值:
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
//闭包引起的循环引用
//循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如 self.someProperty ,或者闭包中调用了实例的某个方法,例如 self.someMethod() 。这两种情况都导致了闭包“捕获” self ,从而产生了循环强引用。
//循环强引用的产生,是因为闭包和类相似,都是引用类型。
//下面的例子为你展示了当一个闭包引用了 self 后是如何产生一个循环强引用的。例子中定义了一个叫 t 的类,用一种简单的模型表示 HTML 文档中的一个单独的元素:
// 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")
// }
//
// }
//解决闭包引起的循环引用
//只要在闭包内使用 self 的成员,就要用 self.someProperty 或者 self.someMethod() (而 不只是 someProperty 或 someMethod() )。这提醒你可能会一不小心就捕获了 self 。
//定义捕获列表
// 捕获列表中的每一项都由一对元素组成,一个元素是 weak 或 unowned 关键字,另一个元素是类实例的引用(例如 self )或初始化过的变量(如 delegate = self.delegate! )。这些项在方括号中用逗号分开。
//如果闭包有参数列表和返回类型,把捕获列表放在它们前面:
//lazy var someClosure: (NSInteger ,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 。这使我们可以在闭包体内检查它们是否存在。
//前面的 HTMLElement 例子中,无主引/弱引用用是正确的解决循环强引用的方法。这样编写 HTMLElement 类来避免循环强 引用:
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: (Void) -> String = {
[unowned self] in//[weak 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")
}
}
网友评论