Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存。
引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。
将实例赋值给属性、常量或变量,都会创建此实例的强引用。只要强引用还在,实例是不允许被销毁的。
如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在。这就是所谓的循环强引用。
可以通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题。
Swift 提供了两种办法用来解决在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。
对于生命周期中会变为nil的实例使用弱引用。相反地,对于初始化赋值后再也不会被赋值为nil的实例,使用无主引用。
弱引用(Weak References)
声明属性或者变量时,在前面加上weak关键字表明这是一个弱引用。
弱引用必须被声明为变量,表明其值能在运行时被修改。弱引用不能被声明为常量。
因为弱引用可以没有值,必须将每一个弱引用声明为可选类型。在 Swift 中,推荐使用可选类型描述可能没有值的类型。
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") }
}
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
无主引用(Unowned References)
和弱引用不同的是,无主引用是永远有值的。因此,无主引用总是被定义为非可选类型(non-optional type)。
可以在声明属性或者变量时,在前面加上关键字unowned表示这是一个无主引用。
ARC 无法在实例被销毁后将无主引用设为nil,因为非可选类型的变量不允许被赋值为nil。
如果试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。使用无主引用,必须确保引用始终指向一个未销毁的实例。
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") }
}
var allen: Customer?
allen = Customer(name: "Allen Appleseed")
allen!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
allen = nil
// 打印 “Allen Appleseed is being deinitialized”
// 打印 ”Card #1234567890123456 is being deinitialized”
无主引用以及隐式解析可选属性(Unowned References and Implicitly Unwrapped Optional Properties)
- 存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为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
}
}
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”
!会引起崩溃,不适合用。这里用可选型或者普通变量都比这个!好
闭包引起的循环强引用(Strong Reference Cycles for Closures)
循环强引用还会发生在将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如self.someProperty,或者闭包中调用了实例的某个方法,例如self.someMethod()。这两种情况都导致了闭包“捕获”self,从而产生了循环强引用。
循环强引用的产生,是因为闭包和类相似,都是引用类型。
Swift 提供了一种优雅的方法来解决这个问题,称之为闭包捕获列表(closure capture list)。
在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。
Swift 有如下要求:只要在闭包内使用self的成员,就要用self.someProperty或者self.someMethod()(而不只是someProperty或someMethod())。这提醒你可能会一不小心就捕获了self。
捕获列表中的每一项都由一对元素组成,一个元素是weak或unowned关键字,另一个元素是类实例的引用(例如self)或初始化过的变量(如delegate = self.delegate!)。这些项在方括号中用逗号分开。
在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用。
相反的,在被捕获的引用可能会变为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")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// 打印 “<p>hello, world</p>”
paragraph = nil
// 打印 “p is being deinitialized”
关于引用循环,尽量用weak;实在没办法,才考了用无主引用。