转载请声明出处:http://blog.csdn.net/jinnchang/article/details/43370549
1、介绍
Swift 使用自动引用计数(ARC)来跟踪并管理应用使用的内存。
当实例不再被使用时,ARC 会自动释放这些类的实例所占用的内存。
注意:ARC 只应用在类的实例。结构体和枚举类型是值类型,不是以引用的方式来存储和传递的。
2、工作原理介绍
每次创建一个类的实例,ARC 就会分配一个内存块,用来存储这个实例的相关信息。这个内存块保存着实例的类型以及这个实例相关的属性的值。
当实例不再被使用时,ARC 自动释放这个实例占用的内存,使这块内存可作它用。这保证了类实例不再被使用时,它们不会占用内存空间。
但是,如果 ARC 释放了仍在使用的实例,那么你就不能再访问这个实例的属性或者调用它的方法。如果你仍然试图访问这个实例,应用极有可能会崩溃。
为了保证不会发生上述的情况,ARC 跟踪与类的实例相关的属性、常量以及变量的数量。只要有一个有效的引用,ARC都不会释放这个实例。
3、强引用
将一个类的实例赋值给一个属性、常量或者变量,这个属性、常量或者变量就是这个实例的强引用。之所以称之为强引用,是因为它强持有这个实例,并且只要这个强引用还存在,就不能销毁实例。
class Person {
let name: String
init(name: String) {
self.name = name
println("\(name) is being initialized")
}
deinit {
println("\(name) is being deinitialized")
}
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
reference1 = Person(name: "John") // John is being initialized
reference2 = reference1
reference3 = reference2
reference1 = nil
reference2 = nil
reference3 = nil // John is being deinitialized
上述代码中,在执行 referenece3 = nil 之前都不会销毁实例,只有当 referenece3 也置为 nil,实例的强引用才完全消失,实例自动被销毁。
4、循环强引用
两个类实例彼此保持对方的强引用,称之为循环强引用。
// 每个人都可拥有一座公寓(可选的,不是必须的)
// 每个公寓都可属于一个人(同样是可选的,不是必须的)
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { println("\(name) is being deinitialized") }
}
class Apartment {
let number: Int
init(number: Int) { self.number = number }
var tenant: Person?
deinit { println("Apartment #\(number) is being deinitialized") }
}
var john: Person?
var number73: Apartment?
john = Person(name: "John Appleseed")
number73 = Apartment(number: 73)
// 用感叹号(!)来展开并访问可选类型的变量,只有这样这些变量才能被赋值
john!.apartment = number73
number73!.tenant = john
john = nil
number73 = nil
注意:因为 Person 和 Apartment 实例之间的强引用依然存在,当上面两个变量赋值为 nil 时,没有调用任何一个 deinitializer。循环强引用阻止了 Person 和 Apartment 实例的销毁,进一步导致内存泄漏。
如何解决循环强引用?
弱引用和无主引用允许循环引用中的一个实例引用另外一个实例,但不是强引用。因此实例可以互相引用但是不会产生循环强引用。
5、弱引用
使用关键字 weak 声明引用为弱引用。
弱引用不会增加实例的引用计数,因此不会阻止 ARC 销毁被引用的实例。
因为运行时它的值可能改变,所以弱引用只能声明为变量类型。弱引用绝对不能声明为常量。
注意:因为弱引用可以没有值,所以声明弱引用的时候必须是可选类型的。
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { println("\(name) is being deinitialized") }
}
class Apartment {
let number: Int
init(number: Int) { self.number = number }
weak var tenant: Person?
deinit { println("Apartment #\(number) is being deinitialized") }
}
var john: Person?
var number73: Apartment?
john = Person(name: "John")
number73 = Apartment(number: 73)
john!.apartment = number73
number73!.tenant = john
john = nil
// prints "John is being deinitialized"
number73 = nil
// prints "Apartment #73 is being deinitialized"
6、无主引用
使用关键字 unowned 声明引用为无主引用。
和弱引用相似,无主引用也不强持有实例。但是和弱引用不同的是,无主引用默认始终有值。
无主引用只能定义为非可选类型,因此当使用无主引用的时候,不需要展开,可以直接访问。
不过非可选类型变量不能赋值为 nil,因此当实例被销毁的时候,ARC 无法将引用赋值为 nil。
注意:当实例被销毁后,试图访问该实例的无主引用会触发运行时错误。使用无主引用时请确保引用始终指向一个未销毁的实例。上面的非法操作会百分百让应用崩溃,不会发生无法预期的行为。因此,你应该避免这种情况。
// 消费者不一定有信用卡,但是每张信用卡一定对应一个消费者
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { println("\(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 { println("Card #\(number) is being deinitialized") }
}
var john: Customer?
john = Customer(name: "John")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
john = nil
// prints "John is being deinitialized"
// prints "Card #1234567890123456 is being deinitialized"
7、隐式展开的可选属性
通过在类型结尾处加感叹号(!),声明该属性为隐式展开的可选类型属性。
// 每个国家都有首都,每个城市都隶属于一个国家
class Country {
let name: String
let 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")
println("\(country.name)'s captial city is called \(country.capitalCity.name)")
Country 的初始化函数调用了 City 的初始化函数。但是,只有 Country 的实例完全初始化完后,Country 的初始化函数才能把 self 传给 City 的初始化函数。
因为 capitalCity 默认值是 nil,一旦 Country 的实例在初始化时给 name 属性赋值后,整个初始化过程就完成了。这代表只要赋值 name 属性后,Country 的初始化函数就能引用并传递隐式的 self。所以,当 Country 的初始化函数在赋值 capitalCity 时,它也可以将 self 作为参数传递给 City 的初始化函数。
8、闭包产生的循环强引用
将一个闭包赋值给类实例的某个属性,并且这个闭包使用了实例,这样也会产生循环强引用。
(这个闭包可能访问了实例的某个属性,例如 self.someProperty,或者调用了实例的某个方法,例如 self.someMethod。)
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 {
println("\(name) is being deinitialized")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
println(paragraph!.asHTML())
paragraph = nil
注意:asHTML 声明为 lazy 属性,因为只有当元素确实需要处理为 HTML 输出的字符串时,才需要使用 asHTML。也就是说,在默认的闭包中可以使用 self,因为只有当初始化完成以及 self 确实存在后,才能访问 lazy 属性。
上述例子中并未打印 deinitialized 的消息,因为该闭包持有了 HTMLElement 实例的强引用,实例无法被销毁。
注意:只要在闭包内使用 self 的成员,就要用 self.someProperty 或者 self.someMethod(而非只是 someProperty 或 someMethod)。这可以提醒你可能会不小心就占有了 self。
9、捕获列表(解决闭包产生的循环强引用)
在定义闭包时同时定义捕获列表作为闭包的一部分,可以解决闭包和类实例之间的循环强引用。
捕获列表中的每个元素都是由 weak 或者 unowned 关键字和实例的引用(如 self 或 someInstance)组成。
// 方式一:捕获列表放置在闭包参数列表和返回类型之前
lazy var someClosure: (Int, String) -> String = {
[unowned self] (index: Int, stringToProcess: String) -> String in
// closure body goes here
}
// 方式二:如果闭包没有指定参数列表或者返回类型,那么捕获列表放在闭包开始的地方,跟着关键字 in
lazy var someClosure: () -> String = {
[unowned self] in
// closure body goes here
}
当闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用。
相反的,当捕获引用有时可能会是 nil 时,将闭包内的捕获定义为弱引用。
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 {
println("\(name) is being deinitialized")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
println(paragraph!.asHTML())
paragraph = nil
注意:如果捕获的引用绝对不会置为 nil,应该用无主引用,而不是弱引用。
10、如何选择引用声明类型
- 两个属性的值都可能是 nil,并有可能产生循环强引用。这种场景下适合使用弱引用。
- 一个属性可以是 nil,另外一个属性不允许是 nil,并有可能产生循环强引用。这种场景下适合使用无主引用。
- 两个属性都必须有值,且初始化完成后不能为 nil。这种场景下,则要一个类用无主引用属性,另一个类用隐式展开的可选属性。
11、结语
文章最后更新时间:2015年2月3日09:27:20。
欲了解更细致的请参考官方文档:Automatic Reference Counting