自动引用计数
Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存
注意
引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。
为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例
example:
class Person {
let name: String
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 Appleseed")
// 打印 "John Appleseed is being initialized"
reference2 = reference1
reference3 = reference1
reference1 = nil
reference2 = nil
reference3 = nil
// 打印 "John Appleseed is being deinitialized"
循环强引用
通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题
强引用example:
对于一般类型的属性,Swfit要求在一个类的初始化方法中保证它一定有值,因此swift中出现循环引用的情况只能是两个变量至少有一个是option类型。
- 两个都是option类型,使用weak弱引用
- 有且只有一个是option类型,使用unowned无主引用
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") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
//产生循环强引用
john!.apartment = unit4A
unit4A!.tenant = john
//不会析构
john = nil
unit4A = nil
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:
* 弱引用(weak reference),声明属性或者变量时,在前面加上weak
关键字表明这是一个弱引用
* 无主引用(unowned reference)
弱引用和无主引用允许循环引用中的一个实例引用而另外一个实例不保持强引用。
这样实例能够互相引用而不产生循环强引用。
- 当其他的实例有更短的生命周期时(也就是其他实例析构在先时),使用弱引用。
- 当其他实例有相同的或者更长生命周期时,使用无主引用
弱引用
弱引用不会保持所引用的实例,即使引用存在,实例也可以被销毁。
因此,ARC
会在引用的实例被销毁后自动将其赋值为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 }
//弱引用,Person的生命周期更短的情况下使用
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
//析构
john = nil
//John Appleseed is being deinitialized
//表明循环引用被打破
unit4A = nil
//Apartment 4A is being deinitialized
注意
在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。但是在ARC
中,如果对象的最后一个强引用被移除,就会被立即销毁,这导致弱引用并不适合上面的用途。
无主引用
和弱引用类似,无主引用不会牢牢保持住引用的实例。
和弱引用不同的是,无主引用在其他实例有相同或者更长的生命周期时使用。
在声明属性或者变量时,在前面加上关键字unowned
表示这是一个无主引用。
无主引用通常都被期望拥有值。
不过 ARC
无法在实例被销毁后将无主引用设为nil
,因为非可选类型的变量不允许被赋值为nil
。
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 无主应用,Customer对象生命周期更长的情况下使用
//当Customer对象被置为nil,CreditCard对象也会被自动回收
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
var customer: Customer?
customer = Customer(name: "customer John Appleseed")
customer!.card = CreditCard(number: 1234_5678_9012_3456, customer: customer!)
//无主引用置为nil
customer = nil
//customer John Appleseed is being deinitialized
//Card #1234567890123456 is being deinitialized
循环引用的第三种场景,无主引用以及隐式解析可选属性
Person和Apartment的例子展示了两个属性的值都允许为
nil
,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决。Customer和CreditCard的例子展示了一个属性的值允许为
nil
,而另一个属性的值不允许为nil
,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决。然而,存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为
nil
。在这种场景中,需要一个类使用无主引用,而另外一个类使用隐式解析可选类型属性。
class Country {
let name: String
//声明为隐式解析可选类型的属性 !
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
//Country的构造函数调用了City的构造函数
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
的构造函数调用了City
的构造函数,然而,只有Country
的实例完全初始化后,Country
的构造函数才能把self
传给City
的构造函数。
为了满足这种需求,通过在类型结尾处加上感叹号City!
的方式,将Country
的capitalCity
属性声明为隐式解析可选类型的属性。这意味着像其他可选类型一样,capitalCity
属性的默认值为nil
,但是不需要展开它的值就能访问它。
由于capitalCity
默认值为nil
,一旦Country
的实例在构造函数中给name
属性赋值后,整个初始化过程就完成了。这意味着一旦name
属性被赋值后,Country
的构造函数就能引用并传递隐式的self
。Country
的构造函数在赋值capitalCity
时,就能将self
作为参数传递给City
的构造函数。
以上的意义在于你可以通过一条语句同时创建Country
和City
的实例,而不产生循环强引用,并且capitalCity
的属性能被直接访问,而不需要通过感叹号来展开它的可选值.
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// 打印 "Canada's capital city is called Ottawa"
闭包引起的循环强引用
循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如self.someProperty
,或者闭包中调用了实例的某个方法,例如self.someMethod()
。这两种情况都导致了闭包“捕获”self
,从而产生了循环强引用。
类似java中的匿名内部类持有外部类引用,但是泄露的机制是不同的,java只需要非静态内部类持有外部类引用就会导致GC不能回收外部类资源,但是Swift的Arc机制是需要闭包和外部类相互循环强引用。
Swift 提供了一种优雅的方法来解决这个问题,称之为闭包捕获列表(closure capture list)
如果闭包有参数列表和返回类型,把捕获列表放在它们前面:
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。这使我们可以在闭包体内检查它们是否存在。
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: (Void) -> String = {
//捕获列表 表示“将self捕获为无主引用而不是强引用”
[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")
}
}