参考书籍:
[1]管蕾.张玲玲.朱元波.Swift开发实战[M]. 北京:人民邮电出版社.2014.10-1 ISBN 978-7-115-36827-0
[2]陈隽.刘媛媛.Swift入门很简单[M]. 北京:清华大学出版社.2015.01-01 ISBN 978-7-302-38880-7
自动引用计数ARC是一种内存管理机制,用来跟踪、管理开发者应用程序所使用的内存。
一、工作机制
开发者每一次创建一个类实例时,ARC会分配一块内存用来管理存储实例相关的信息,比如实例的类型信息、与实例相关的属性值。当实例不再被使用时,ARC将会释放此实例所占的内存。这样保证不会使用的内存不会一直占用内存空间。
示例代码:
import Foundation class Person{ let name:String init(name: String){ self.name = name print("\(name) start to init.") } deinit { print("\(name) was freed ") } } var referencel: Person? = Person(name: "John")//创建实例时,ARC将会分配内存 referencel = nil//设置为nil是,ARC释放内存
John start to init.
John was freed
Program ended with exit code: 0
修改版1,访问被释放的实例属性:
import Foundation class Person{ let name:String init(name: String){ self.name = name print("\(name) start to init.") } deinit { print("\(name) was freed ") } } var referencel: Person? = Person(name: "John") referencel = nil print(referencel?.name)
终端如下:
John start to init.
John was freed
nil
Program ended with exit code: 0
解释:访问已经被释放的实例或方法,将会得到nil
修改版2,强制访问被释放的实例属性:
import Foundation class Person{ let name:String init(name: String){ self.name = name print("\(name) start to init.") } deinit { print("\(name) was freed ") } } var referencel: Person? = Person(name: "John") referencel = nil print(referencel!.name)
John start to init.
John was freed
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
即,强制访问会导致程序崩溃。
二、循环强引用
开发者可能会写出“在程序中使用多个强引用”,而这些强引用不知不觉形成循环强引用,而导致内存泄露。
1、类实例之间循环强引用
多个类互相保留对方的强引用,并让对方不被销毁。例如:
import Foundation class Person{ let name:String init(name: String){ self.name = name print("\(name) start to init.") } var apartment: Apartment? //属性声明 deinit { print("\(name) was freed ") } } class Apartment{ let number: Int init(number: Int){ self.number = number } var tenant: Person?//属性声明 deinit{ print("Apartment #\(number) was freed") } } var referencel: Person? = Person(name: "John") var number: Apartment? = Apartment(number: 74) print(referencel?.name) print(number?.number) referencel!.apartment=number//为对象中的属性赋值 number!.tenant = referencel//为对象中的属性赋值 referencel = nil number = nil
终端如下:
John start to init.
Optional("John")
Optional(74)
Program ended with exit code: 0
上例,两个实例创建循环强引用,导致引用计数并不会将为0,实例未被ARC销毁。
2、闭包引起的循环强引用
即,开发者将一个闭包赋值给类实例的某个属性时,在这个闭包体中又使用了实例,或者闭包体中可能访问了实例的某个属性、方法,导致产生循环强引用。
例如:
import Foundation 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) was freed") } } var paragraph: HTMLElement? = HTMLElement(name: "p" , text: "Hello,world") print(paragraph!.asHTML()) paragraph = nil
终端:
<p>Hello,world</p>
Program ended with exit code: 0
上面例子中,闭包实现的功能是赋值给asHTML属性,返回一个代表HTML标签的字符串。
使用HTMLElement创建的实例和asHTML默认值的闭包之间的循环强引用,使得paragraph设置为nil时,ARC没有释放实例。
HTMLElement类的实例name:"p" - strong -> ()->String中self.name
HTMLElement类的实例text:"hello world"<- strong - ()->String中self.text
三、循环强引用解决方法
1、解决实例之间的循环强引用
1.1 弱引用
弱引用不会牢牢保留住引用实例,不会阻止ARC销毁被引用的实例,这种行为阻止了引用变为循环强引用。
例如:
import Foundation class Person{ let name:String init(name: String){ self.name = name print("\(name) start to init.") } var apartment: Apartment? deinit { print("\(name) was freed ") } } class Apartment{ let number: Int init(number: Int){ self.number = number } weak var tenant: Person?//使用weak关键字(必须为可变存储属性) deinit{ print("Apartment #\(number) was freed") } } var referencel: Person? = Person(name: "John") var number: Apartment? = Apartment(number: 74) print(referencel?.name) print(number?.number) referencel!.apartment=number number!.tenant = referencel referencel = nil number = nil
终端如下(成功释放):
John start to init.
Optional("John")
Optional(74)
John was freed
Apartment #74 was freed
Program ended with exit code: 0
1.2 无主引用
无主引用也不会保留引用的实例,和弱引用不同的是,无主引用永远有值。因此无主引用总是被定义为非可选类型。
开发者声明属性或者变量/常量是,在前面加上关键字unowned表示这是一个无主引用,语法形式:
unowned let/var 属性/常量/变量:实例
1.3 无主引用和隐式解析可选属性
2、解决闭包所引起的循环强引用
闭包所引起的循环强引用,可以通过定义闭包的同时定义捕获列表作为闭包的一部分解决。捕获列表定义了闭包内捕获一个或者多个引用类型的规则。跟解决类实例键的循环强引用一样,需要声明每个捕获的引用为弱引用或无主引用。
2.1定义捕获列表
捕获列表中每个元素都是由weak 或unowned关键字和实例引用(如self或someInstance)成对组成。每一对都是方括号内,通过逗号分开,定义形式如下:
[关键字 self]
2.2若引用和无主引用
当闭包和捕获的实例总是相互作用,并且总是同时销毁时,将闭包内的捕获定义为无主引用;相反,当捕获引用有时可能为nil时,将闭包内捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用值会自动置为nil。这可以让开发者在闭包内检查他们是否存在。
例如:使用捕获列表解决循环强引用
import Foundation 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) was freed") } } var paragraph: HTMLElement? = HTMLElement(name: "p" , text: "Hello,world") print(paragraph!.asHTML()) paragraph = nil
终端如下(成功释放):
<p>Hello,world</p>
p was freed
Program ended with exit code: 0