自动引用计数
swift使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存。通常情况下,Swift内存管理机制会一直起作用,我们无须自己来考虑内存的管理。ARC会在类的实例不再被使用时,自动释放其占用的内存。
note:引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。
自动引用计数的工作机制
为了确保使用的实例不会被销毁,ARC会跟踪和计算每一个实例正在被多数属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。
为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称为“强”引用,是因为它会将实例牢牢的保持住,只要强引用还在,实例就不允许被销毁的。
类实例之间的循环强引用
我们可能会写出一个类实例的强引用数永远不能变成0的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的循环强引用。
我们可以通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题。
class A{
let name:String
init(name:String){
self.name = name
}
var b: B?
deinit{
print("a \(name) is dead")
}
}
class B {
let name:String
init(name:String) {
self.name = name
}
var a:A?
deinit{
print("b \(name) is dead")
}
}
func test(){
let a = A(name: "obj a")
let b = B(name: "obj b")
a.b = b
b.a = a
}
test()
解决实例之间的循环强引用
Swift提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。
弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够相互引用而不产生循环强引用。
对于生命周期中会变为nil的实例使用弱引用。相反地,对于初始化赋值后再也不会被赋值为nil的实例,使用无主引用。
弱引用
弱引用不会对引用的实例保持强引用,因而不会阻止ARC销毁被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上weak关键字声明这是一个弱引用。
在实例的生命周期中,如果某些时候引用没有值,哪么弱引用可以避免循环强引用。如果引用总是有值,则可以使用无主引用。
note: 弱引用必须被声明为变量,表明其值能在运行时被修改。弱引用不能被声明为常量。 弱引用可以没有值,我们必须将每一个弱引用声明为可选类型。在Swift中,推荐使用可选类型描述可能没有值的类型。
因为弱引用不会保持所引用的实例,即使引用存在,实例也有可能被销毁。因此,ARC会在引用的实例被销毁后自动将其赋值为nil。
class A{
let name:String
init(name:String){
self.name = name
}
var b: B?
deinit{
print("a \(name) is dead")
}
}
class B {
let name:String
init(name:String) {
self.name = name
}
weak var a:A?
deinit{
print("b \(name) is dead")
}
}
func test(){
let a = A(name: "obj a")
let b = B(name: "obj b")
a.b = b
b.a = a
}
test()
无主引用
和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用是永远有值的。因此,无主引用总被定义为非可选类型。我们可以在声明属性或者变量时,在前面添加关键字unowned表示这是一个无主引用。
由于无主引用是非可选类型,我们不需要在使用它的时候将其展开。无主引用总是可以被直接访问。不过ARC无法在实例被销毁后将无主引用设为nil,因为非可选类型的变量不允许被赋值为nil。
note:如果我们试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。使用无主引用,我们必须确保引用始终指向一个未销毁的实例。
还需要注意的是如果我们试图访问实例已经被销毁的无主引用,Swift确保程序会直接奔溃,而不会发生无法预期的行为。所以我们应当避免这样的事情发生。
class A{
let name:String
init(name:String){
self.name = name
}
var b: B?
deinit{
print("a \(name) is dead")
}
}
class B {
let name:String
init(name:String,a:A ) {
self.name = name
self.a = a
}
unowned var a:A
deinit{
print("b \(name) is dead")
}
}
func test(){
let a = A(name: "obj a")
let b = B(name: "obj b",a:a)
}
test()
无主引用以及隐式解析可选属性
还存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完后永远不会为nil。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解析可选属性。
这使两个属性在初始化完成后被直接访问(不需要可选展开),同时避免了循环引用。
class Country {
let name: String
var capitalCity: City!
init(name:String,capitalCityName:String){
self.name = name
self.capitalCity = City(name: capitalCityName, country: self)
}
deinit{
print("country dead")
}
}
class City{
let name:String
unowned let country: Country
init(name:String,country:Country){
self.name = name
self.country = country
}
deinit{
print("city dead")
}
}
func test(){
var country = Country(name: "china", capitalCityName: "beijing")
let city = country.capitalCity
print("\(country.name) and \(country.capitalCity.name)")
print("\(city.country.name)and \(city.country.capitalCity.name) ")
}
test()
闭包引起的循环强引用
循环强引用还会发生在当我们将一个闭包赋值给类实例的某个属性,并且这个闭包中又使用了这个类的实例。这个闭包中可能访问了实例的某个属性(self.someProperty),或者闭包中调用了实例的某个方法(self.someMethod),这两种情况都导致闭包捕获self,从而产生了循环强引用。
循环强引用的产生,是因为闭包和类相似,都是引用类型。当我们把一个闭包赋值给某个属性时,你也把一个引用赋值给这个闭包。实质上,这跟之前的问题一样的---两个强引用让彼此一有效。
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 dead")
}
}
func test(){
var html = HTMLElement(name: "h1", text: "hello world!")
print(html.asHTML())
}
test()
note:虽然闭包多次使用了self,它只捕获HTMLElement实例的一个强引用。
解决闭包引起的循环强引用
在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。
note:swift有如下要求:只要闭包内使用self的成员,就要用self.someproperty或者self.someMethod(),而不是someproperty或someMethod()。这提醒我们可能一不小心就捕获了self。
定义捕获列表
捕获列表中的每一项都由一个对元素组成,一个元素是weak或unowned关键字,另外一个元素是类实例的引用(如self)或初始化过的变量(如delegate = self.delegate!)。这些项在放括号中用逗号分开。
class HTMLElement{
let name:String
let text:String?
lazy var asHTML: Void ->String = {
[weak weakSelf = self] in
if let text = weakSelf!.text {
return "<\(weakSelf!.name)> \(text)\(weakSelf!.name)>"
}else{
return "\(weakSelf!.name)>"
}
}
init(name:String,text:String? = nil){
self.name = name
self.text = text
}
deinit{
print("\(name) is dead")
}
}
func test(){
var html:HTMLElement? = HTMLElement(name: "h1", text: "hello world!")
print(html!.asHTML())
html = nil
}
test()
如果闭包有参数列表或返回类型,把捕获列表放在它们前面:
lazy var someClosure: (Int,String)->String = {
[unowned self,weak delegate = self.delegate!] (index:Int,StringToProcess:String)-> String in
//closure statement
}
```
如果闭包没有指明参数列表或者返回类型,即它们会通过上下文推断,哪么可以把捕获列表和关键字in放在闭包最开始的地方:
lazy var someClosure: Void ->String = {
[unowned self,weak delegate = self.delegate!] in
//closure statement
}
```
弱引用和无主引用
在闭包和捕获的实例总是相引用时并且总是同时销毁时,将闭包内的捕获定义为无主引用。
相反的,在捕获的引用可能会变为nil时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会被自动设置为nil。这使我们可以在闭包体内检查它们是否存在。
note:如果被捕获的引用绝对不会变为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 dead")
}
}
func test(){
var html:HTMLElement? = HTMLElement(name: "h1", text: "hello world!")
print(html!.asHTML())
html = nil
}
test()