Swift 中的自动引用计数(一)
自动引用计数工作
每次当实例化一个对象时,ARC就会给这个对这个实例分配一段内存来存储信息。这段内存信息包含实例的类型和这个实例的存储属性的信息。
当这个实例不在被需要时,ARC会释放这部分内存。然而,如果ARC来释放了哪些仍然使用的实例时,那这些实例的属性和方法将不会在被访问。
为了确保实例不会被销毁,ARC跟踪有多少属性、常量和变量指向每个类的实例。
ARC in Action
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var ref1: Person?
var ref2: Person?
var ref3: Person?
ref1 = Person(name: "John Appleseed")
ref2 = ref1
ref3 = ref1 // 3 strong reference
可以看个例子,这里有个Person类,包括一个构造函数和一个析构函数。下面定义了三个Person可选类型的变量,注意对于可选变量是没有调用初始化函数的。
下面将新构造的Person实例赋值给ref1,这时会调用Person的初始化函数,这样 "John Appleseed is being initialized"就被打印出来。
因为新构造的Person实例被赋值ref1,所以会有一个强引用由ref1只想新构造的Person实例。这样ARC会确保新构造的Person实例一直在内存不会被解构。
如果将Person实例赋值给其他的变量,如ref2、ref3。这样会多出两个指向该类型的强引用。
ref1 = nil
ref2 = nil
ref3 = nil // Prints "John Appleseed is being deinitialized"
只有当ref3指向一个空时,会调用析构函数,这样 "John Appleseed is being deinitialized"就会被打印。
类和实例间的循环强引用
如果两个类实例都拥有对方的强引用这样就会触发循环强引用(strong reference cycle)。
这里有一个例子,
class Person {
let name: String
init(name: String) {
self.name = name
}
var apertment: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有一个对新Person的强引用;unit4A有一个对新Apartment的强引用。
通过下面的代码将两个实例进行连接。注意!用来解包和访问一个实例里的可选属性。
john!.apertment = unit4A
unit4A!.tenant = john
这样就产生了强循环引用,
如果,我们释放掉john和unit4A,引用计数不会降到0,这样实例就不会被释放
john = nil
unit4A = nil
循环引用的解决方案
Swift 提供两种方式来解决循环引用:weak reference 和 unowned reference。
Weak 和 unowned 引用能够使一个循环引用中的一个实例指向另外一个实例但是不会保持强引用。
对于weak reference ,一般适合另外一个实例有个较短的声明周期。相反,unowned reference 适合另外一个实例有相同的或者更长的生命周期。
Weak reference
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 // John Appleseed is being deinitialized
unit4A = nil // Apartment 4A is being deinitialized
因为weak reference 不会保持一个实例间的强链接,所以可以保证当实例释放时,仍然会有一个weak reference 指向它。ARC会自动的将weak reference指向nil。这里将Aparment 中的tenant设置为weak reference
这样 Person的实例有一个强引用指向Apartment,Apartment有一个weak reference 指向Person。这意味着当你要释放掉john这个变量时,就没有任何一个强引用指向Person。
- Note
- In systems that use garbage collection, weak pointers are sometimes used to implement a simple caching mechanism because objects with no strong references are deallocated only when memory pressure triggers garbage collection. However, with ARC, values are deallocated as soon as their last strong reference is removed, making weak references unsuitable for such a purpose.
Unowned Reference
与weak reference 不同的地方在于,被标记为unowned的值将不能为可选类型,也就是说被标记为unowned的变了永远都会有值。ARC将不会将unowned reference赋值为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 john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
john = nil
// John Appleseed is being deinitialized
// Card #1234567890123456 is being deinitialized
可以看看上面代码中四个变量时如何引用的
Customer实例有一个强引用指向CreditCard,而CreditCard有一个unowned reference 指向Customer。当你释放掉john的时候,就不会有强引用指向Customer实例了,这样Customer就会被释放掉。
Customer实例被释放掉后,也没有强引用指向CreditCard,这样它也会被释放掉
-
Note
-
The examples above show how to use safe unowned references. Swift also provides unsafe unowned references for cases where you need to disable runtime safety checks—for example, for performance reasons. As with all unsafe operations, you take on the responsibility for checking that code for safety.
You indicate an unsafe unowned reference by writing
unowned(unsafe). If you try to access an unsafe unowned reference after the instance that it refers to is deallocated, your program will try to access the memory location where the instance used to be, which is an unsafe operation.
-
Unowned Optional References
当然swift的ARC模型中,你也可以使用可选的unowned reference。区别在于你需要自己将其指向一个有效的对象或者设置为nil
class Department {
var name: String
var courses: [Course]
init(name: String) {
self.name = name
self.courses = []
}
}
class Course {
var name: String
unowned var department: Department
unowned var nextCourse: Course?
init(name: String, in department: Department) {
self.name = name
self.department = department
self.nextCourse = nil
}
}
let department = Department(name: "Horticulture")
let intro = Course(name: "Survey of plants", in: department)
let intermediate = Course(name: "Growing Commen Herbs", in: department)
let advaced = Course(name: "Caring for torpical plants", in: department)
intro.nextCourse = intermediate
intermediate.nextCourse = advaced
department.courses = [intro, intermediate, advaced]
上面代码的引用关心如下图
一个unowned optional referenc 不会在当类实例要释放掉时候继续保持一个强引用的关心,所以这样会阻止ARC释放掉这个实例。
Unowned References and Implicitly Unwrapped Optional Properties
闭包中的循环引用
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()) // Prints "<h1>some default text</h1>"
如上面代码所示,HTMLElement实例中间会出现循环引用,如下图所示
实例中的asHTML是一个对闭包类型的强引用。然而,因为在闭包内调用了self(需要访问self.name, self.text),闭包将捕获到self,这样会形成一个指向HTMLElement实例的引用。这就形成了循环引用。
如果你将paragraph设为nil,实例和闭包也不会被释放。
闭包循环引用的解决方法
-
定义一个捕获列表
捕获列表(
capture list) -
使用weak和unowned引用