ARC是如何工作的
每次创建一个新的类的实例,ARC便会分配一块内存来存储实例的信息
当一个实例不再需要,ARC便会释放掉它的内存
为了做到这一点,当将一个类的实例分配给一个属性、常量或变量,该属性、常量或变量都会强引用该实例。该实例不被释放直到没有强引用。
循环引用
循环引用发生在实例之间的相互引用
循环引用的例子
首先创建了两个类
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和unit4A的强引用时,引用计数不会降为0,并且实例不会被ARC释放掉
john = nil
unit4A = nil
如何解决循环引用
在swift中有两种解决循环引用的方式:weak引用和unowned引用
weak 和 unowned 不会对实例保持强引用
使用weak的情景:当被引用的实例的生命周期相对短,因此该实例被首先释放
使用unowned的情景:当被引用的实例有同样生命周期或相对长
Weak 引用
因为weak 引用不会强引用实例,所以仍然被weak引用的实例有可能被释放掉。当weak引用的实例被释放时,ARC自动设置此weak引用为nil。因为weak引用需要允许它的值变为nil,所以它们总是声明为可选变量
注意:当ARC将弱引用设置为nil时,不会调用属性观察器。
这个例子与上面的区别仅仅是设置tenant变量为weak引用
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 引用
和weak引用一样,不会强引用实例。不一样的是,unowned引用用于被引用 的实例的有相同的生命的周期或相对长。
一个unowned引用被期望总是有值的,因此,ARC决不会设置unowned引用为nil,意味着unowned不定义为可选类型。
重要:仅仅当被引用的变量总是不会释放时使用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 let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
设置Customer实例为nil,Customer便没有了强引用被释放掉,然后CreditCard实例也没有了强引用被释放
john = nil
闭包的强引用
闭包会捕捉变量并强引用变量
闭包是引用类型,当分配一个闭包给实例的属性,此实例就强引用了闭包,如果闭包访问了此实例的属性,例如self.someProperty
,闭包会捕捉self并强引用,此时循环引用就发生了
循环引用产生的例子
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")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
paragraph = nil
实例引用计数不为0不会释放
如何解决闭包的循环引用
定义捕获列表:
捕获列表中的实例用weak或unowned关键字来修饰,写在一对方括号内,用逗号隔开。
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// closure body goes here
}
lazy var someClosure: () -> String = {
[unowned self, weak delegate = self.delegate!] in
// closure body goes here
}
使用weak引用:捕捉的变量有可能为nil,意味着被捕捉的变量的生命周期短于闭包
使用unowned引用:总是相互引用,并同时释放,说明被捕捉的变量的生命周期等于大于闭包
通过定义捕捉变量来解决循环引用
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> 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())
当设置HTMLElement实例的为nil时,此实例便被释放
paragraph = nil