十六 自动引用计数html
ARCswift
Swift使用自动引用计数(ARC)来管理应用程序的内存使用。这表示内存管理已是Swift的一部分,在大多数状况下,你并不须要考虑内存的管理。当实例并再也不被须要时,ARC会自动释放这些实例所使用的内存。闭包
可是,少数状况下,你必须提供部分代码的额外信息给ARC,这样它才可以帮你管理这部份内存。本章阐述了这些状况而且展现如何使用ARC来管理应用程序的内存。app
注意函数
引用计数仅仅做用于类实例上。结构和枚举是值类型,而非引用类型,因此不能被引用存储和传递。学习
一、ARC怎样工做spa
每当你建立一个类的实例,ARC分配一个内存块来存储这个实例的信息,包含了类型信息和实例的属性值信息。htm
另外当实例再也不被使用时,ARC会释放实例所占用的内存,这些内存能够再次被使用。对象
可是,若是ARC释放了正在被使用的实例,就不能再访问实例属性,或者调用实例的方法了。直接访问这个实例可能形成应用程序的崩溃。blog
为了保证须要实例时实例是存在的,ARC对每一个类实例,都追踪有多少属性、常量、变量指向这些实例。当有活动引用指向它时,ARC是不会释放这个实例的。
为实现这点,当你将类实例赋值给属性、常量或变量时,指向实例的一个强引用(strong reference)将会被构造出来。被称为强引用是由于它稳定地持有这个实例,当这个强引用存在是,实例就不可以被释放。
2、ARC实例
下面的例子展现了ARC是怎样工做的。定义一个简单的类Person,包含一个存储常量属性name:
class Person { let name: String init(name: String) { self.name = name print("\(name) is being initialized") } deinit { print("\(name) is being deinitialized") } }
Person类有一个初始化方法来设置属性name并打印一条信息代表这个初始化过程。还有一个析构方法打印实例被释放的信息。
下面的代码定义了是三个Person?类型的变量,随后的代码中,这些变量用来设置一个Person实例的多重引用。由于这些变量是可选类型(Person?),它们自动被初始化为nil,而且不该用任何Person实例。
var reference1: Person? var reference2: Person? var reference3: Person? reference1 = Person(name: "John Appleseed")
prints "John Appleseed is being initialized"
注意这条信息:““John Appleseed is being initialized”,指出类Person的构造器已经被调用。
由于新的Person实例被赋值给变量reference1,所以这是一个强引用。因为有一个强引用的存在,ARC保证了Person实例在内存中不被释放掉。
若是你将这个Person实例赋值给更多的变量,就创建了相应数量的强引用:
reference2 = reference1 reference3 = reference1
如今有三个强引用指向这个Person实例了。
若是你将nil赋值给其中两个变量从而切断了这两个强引用(包含原始引用),还有一个强引用是存在的,所以Person实例不被释放。
reference1 = nil reference2 = nil
直到第三个强引用被破坏以后,ARC才释放这个Person实例,所以以后你就不能在使用这个实例了:
reference3 = nil
prints "John Appleseed is being deinitialized"
三、类实例间的强引用循环
在上面的例子中,ARC跟踪指向Person实例的引用并保证只在Person实例再也不被使用后才释放。
可是,写出一个类的实例没有强引用指向它这样的代码是可能的。试想,若是两个类实例都有一个强引用指向对方,这样的状况就是强引用循环。
四、解决类实例之间的强引用循环
Swift提供了两种方法解决类实例属性间的强引用循环:弱引用和无主(unowned)引用。
弱引用和无主引用使得一个引用循环中实例并不须要强引用就能够指向循环中的其余实例。互相引用的实例就不用造成一个强引用循环。
当在生命周期的某些时刻引用可能变为nil时使用弱引用。相反,当引用在初始化期间被设置后再也不为nil时使用无主引用。
弱引用
弱引用并不保持对所指对象的强烈持有,所以并不阻止ARC对引用实例的回收。这个特性保证了引用不成为强引用循环的一部分。指明引用为弱引用是在生命属性或变量时在其前面加上关键字weak。
无主引用
和弱引用同样,无主引用也并不持有实例的强引用。但和弱引用不一样的是,无主引用一般都有一个值。所以,无主引用并不定义成可选类型。指明为无主引用是在属性或变量声明的时候在以前加上关键字unowned。
由于无主引用非可选类型,因此每当使用无主引用时没必要解开它。无主引用一般能够直接访问。可是当无主引用所指实例被释放时,ARC并不能将引用值设置为nil,由于非可选类型不能设置为nil。
注意
在无主引用指向实例被释放后,若是你像访问这个无主引用,将会触发一个运行期错误(仅当可以确认一个引用一直指向一个实例时才使用无主引用)。在Swift中这种状况也会形成应用程序的崩溃,会有一些不可预知的行为发生,尽管你可能已经采起了一些预防措施
接下来的例子定义了两个类,Customer和CreditCard,表示一个银行客户和信用卡。这两个类的属性各自互相存储对方类实例。这种关系存在着潜在的强引用循环。
在此例中,一个客户可能有也可能没有一个信用卡,可是一个信用卡必须由一个客户持有。所以,类Customer有一个可选的card熟悉,而类CreditCard有一个非可选customer属性。
另外,建立CreditCard实例时必须必须向其构造器传递一个值number和一个customer实例。这保证了信用卡实例总有一个客户与之联系在一块儿。
由于信用卡总由一个用户持有,因此定义customer属性为无主引用,来防止强引用循环。
class Customer { let name: String var card: CreditCard? init(name: String) { self.name = name } deinit { print("\(name) is being deinitialized") } } class CreditCard { let number: Int unowned let customer: Customer init(number: Int, customer: Customer) { self.number = number self.customer = customer } deinit { print("Card #\(number) is being deinitialized") } }
下面的代码段定义了Customer类型可选变量john,用来存储一个特定用户的引用,这个变量初值为nil:
var john: Customer?
如今能够建立一个Customer实例,并初始化一个新的CreditCard实例来设置customer实例的card属性:
john = Customer(name: "John Appleseed") john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
Customer实例有一个到CreditCard实例的强引用,CreaditCard实例有一个到Customer实例无主引用。
由于无主引用的存在,当你破坏变量john持有的强引用时,就再也没有到Customer实例的强引用了。
由于没有到Customer实例的强引用,实例被释放了。以后,到CreditCard实例的强引用也不存在了,所以这个实例也被释放了:
john = nil
prints Card #1234567890123456 is being deinitialized
prints John Appleseed is being deinitialized
上面的代码段显示了变量john设置为nil后Customer实例和CreditCard实例被析构的信息。
无主引用和隐式拆箱可选属性?
可是两个属性都一直有值,而且都不能够被设置为nil。这种状况下,一般联合一个类种的无主属性和一个类种的隐式装箱可选属性(implicitly unwrapped optional property)。
这保证了两个属性均可以被直接访问,而且防止了引用循环。
五、闭包的强引用循环
当将一个闭包赋值给一个类实例的属性,而且闭包体捕获这个实例时,也可能存在一个强引用循环。捕获实例是由于闭包体访问了实例的属性,就像self.someProperty,或者调用了实例的方法,就像self.someMethod()。无论哪一种状况,这都形成闭包捕获self,形成强引用循环。
这个强引用循环的存在是由于闭包和类同样都是引用类型。当你将闭包赋值给属性时,就给这个闭包赋值了一个引用。本质上和前面的问题相同-两个强引用都互相地指向对方。可是,与两个类实例不一样,这里是一个类与一个闭包。
Swift为这个问题提供了一个优美的解决方法,就是闭包捕获列表。可是,在学习怎样经过闭包捕获列表破坏强引用循环之前,有必要了解这样的循环是怎样形成的。
下面的例子展现了当使用闭包引用self时强引用循环是怎样形成当。定义了一个名为HTMLElement的类,建模了HTML文档中的一个单独的元素:
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") } }
这个HTMLElement类定义了一个表示元素(例如“p“,”br“)名称的属性name,和一个可选属性text,表示要在页面上渲染的html元素的字符串的值
另外,还定义了一个懒惰属性asHTML。这个属性引用一个闭包,这个闭包结合name与text造成一个html代码字符串。这个属性类型是()-> String,表示一个函数不须要任何参数,返回一个字符串值。
默认地,asHTML属性赋值为返回HTML标签字符串的闭包。这个标签包含了可选的text值。对一个段落而言,闭包返回”<p>some text</p>”或者”<p />”,取决其中的text属性为“some text”仍是nil。
asHTML属性的命名和使用都和实例方法相似,可是,由于它是一个闭包属性,若是想渲染特定的html元素,你可使用另一个闭包来代替asHTML属性的默认值。
这个HTMLElement类提供单一的构造器,传递一个name和一个text参数。定义了一个析构器,打印HTMLElement实例的析构信息。
下面是如何使用HTMLElement类来建立和打印一个新的实例:
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") print(paragraph!.asHTML())
打印 <p>hello, world</p>
不幸的是,上面所写的HTMLElemnt类的实现会在HTMLElement实例和闭包所使用的默认asHTML值之间形成强引用循环
实例的asHTML属性持有其闭包的一个强引用,可是由于闭包在其类内引用self(self.name和self.text方式),闭包捕获类自己,意味着它也持有到HTMLElement实例的引用。强引用循环就这样创建了。
若是设置paragraph变量值为nil,破坏了到HTMLElement实例的强引用,实例和其闭包都不会被析构,由于强引用循环:
paragraph = nil
注意HTMLElement析构器中的提示信息不会被打印,表示HTMLElement实例并无被析构。
六、解决闭包的强引用循环
经过定义捕获列表为闭包的一部分能够解决闭包和类实例之间的强引用循环。捕获列表定义了在闭包体内什么时候捕获一个或多个引用类型的规则。像解决两个类实例之间的强引用循环同样,你声明每一个捕获引用为弱引用或者无主引用。究竟选择哪一种定义取决于代码中其余部分间的关系
定义捕获列表
捕获列表中的每一个元素由一对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,定义捕获列表为弱引用。弱引用一般是可选类型,而且在实例释放后被设置为nil。这使得你能够在闭包体内检查实例是否存在。
在例子HTMLElement中,可使用无主引用来解决强引用循环问题,下面是其代码:
class HTMLElement1 { 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") } }
这个HTMLELement实如今以前的基础上在asHTML闭包中加上了捕获列表。这里,捕获列表是[unowned self],表示做为无主引用来捕获本身而不是强引用。
var paragraph1: HTMLElement1? = HTMLElement1(name: "p", text: "Hello, World!") print(paragraph1!.asHTML())
打印<p>Hello, World!</p>
此时,闭包捕获自身是一个无主引用,并不持有捕获HTMLelement实例的强引用。若是你设置paragraph的强引用为nil,HTMLElement实例就被释放了,能够从析构信息中看出来:
paragraph1 = nil
打印p is being deinitialized