本篇介绍Swift的基础知识:通过可选链(optional chaining)调用属性,方法和下标;自动引用计数(automatic reference counting)的工作机制;循环强引用的解决方案。
Title: Swift基础入门(9):可选链自动引用计数
Author: Yunyao Zhang(张云尧)
E-mail: aidaizyy@gmail.com
Last Modified: 2015-07-24
可选链
如果请求和调用属性,方法和下标的目标可能为空nil,这样的多次请求或调用就可以被链接起来,称为可选链。
如果任何一个节点为空nil,整个可选链失效。
创建了Person的实例john,john中包含类Residence的实例resindence。resindence包含了可选类型的属性和方法,值可能为nil,所以访问它的属性,方法和下标时都应该加上?。
可选链中只要有一个节点为可选类型,可选链的结果就一定为可选类型。
第39行,john.residence?.numberOfRooms的结果类型为Int?。
第47行,john.residence?.printNumberOfRooms()的结果类型为void?。这里不能直接用函数结构作为布尔型去判断,而是与nil比较。
第55行,john.residence?.getId()的结果类型为String?。
第63行,john.residence?[0].name的结果类型为String?。这里的?放在[0]前,因为确保数组有值,才能通过下标去访问。
第71行,john.reidence?.address?.street的结果类型为String?。多层的可选链接链接到一起,residence和address都是可选类型,所以使用了两个?。如果给john.residence.address中的address分配实例,应该写作john.residence!.address,强制解析确保residence有值,才能对其中的address分配实例。
自动引用计数
自动引用计数(ARC)跟踪和管理内存,会自动释放不再使用的实例占用的内存。
ARC会跟踪和计算每一个实例被多少属性,常量和变量引用,这样的引用称为对实例的强引用。不存在强引用,实例会被销毁,否则实例会被保留。
类实例的循环强引用
两个类中相互引用,相互保持对方的强引用,这样无法销毁,形成了循环强引用。
强引用
|
|
上面的代码展示了类实例的循环强引用。
类Person中有属性是Apartment类型,类Apartment中有属性是Person类型。
声明了实例john和实例number73,并赋值。
最后两行把两个实例都设为nil,但是析构函数并没有被调用,因为两个实例还有循环强引用联系,并没有自动销魂,而且造成了内存泄露。
弱引用
为了解决循环强引用问题,有两种办法:弱引用(weak reference)和无主引用(unowned reference)。
一个实例对另一个实例弱引用或者无主引用,不产生强引用,反过来,另一个实例对一个实例强引用,这样能够相互引用而不产生循环强引用。如果实例的值可能为nil使用弱引用;如果实例的值不可能为nil使用无主引用。
声明时在属性或常量变量前加上weak关键字表示弱引用。
两个实例的值都可能为nil,使用弱引用。
上面的代码和循环强引用代码基本一致,只是在类Apartment的类型为Person?的属性tenant前加上了weak。因为Person?是可选类型,tenant值可能为nil,所以使用弱引用。
最后一句,赋值nil给john后,因为number73对john不是强引用,john这时没有强引用,可以销毁,调用了析构函数。john销毁后,number73没有强引用,也可以被销毁。
如果把number73 = nil和john = nil语句顺序交换,打印顺序不会变,因为没有强引用的john`一定是先销毁。
无主引用
声明时在属性或常量变量前加上unowned关键字表示无主引用。
- 一个实例的值可能为
nil,另一个实例的值不可能为nil。123456789101112131415161718192021222324252627class Customer {let name: Stringvar card: CreditCard?init(name: String) {self.name = name}deinit { println("\(name) is being deinitialized") }}class CreditCard {let number: Intunowned let customer: Customerinit(number: Int, customer: Customer) {self.number = numberself.customer = customer}deinit { println("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// prints "John Appleseed is being deinitialized"// prints "Card #1234567890123456 is being deinitialized"
类CreditCard的类型为Customer的属性customer前加上了unowned。因为customer在构造函数中就会赋予初始值,值不会为nil,所以使用无主引用。
最后一句,赋值nil给john后,因为类CreditCard的实例对john不是强引用,john这时没有强引用,可以销毁,调用了析构函数。john销毁后,类Creditcard的实例也没有强引用了,跟着被销毁了。
- 两个实例的值都不可能为
nil。
这种场景一个类使用无主属性,另一个类使用隐式解析可选类型。123456789101112131415161718192021class Country {let name: Stringlet capitalCity: City!init(name: String, capitalName: String) {self.name = nameself.capitalCity = City(name: capitalName, country: self)}}class City {let name: Stringunowned let country: Countryinit(name: String, country: Country) {self.name = nameself.country = country}}var country = Country(name: "Canada", capitalName: "Ottawa")println("\(country.name)'s capital city is called \(country.capitalCity.name)")// prints "Canada's capital city is called Ottawa"
类City的类型为Country的属性country前加上了unowned表示无主属性。
类Country的类型为City!的属性capitalCity在类型后加上了!表示隐式解析可选类型。
类Country的实例的country创建时,调用构造器,为name和capiptalCity赋值。因为self属性必须在构造的第二阶段使用,也就是类中所有存储型属性全部有初始值之后才能使用。如果Country的值可以为nil,先给capitalCity赋值nil就可以调用构造器为capitalCity赋予新值。但是这里不可以赋值nil,所以加上了!隐私解析可选类型,默认初始值为nil,可以调用构造器赋予新值。
属性capitalCity在调用时可以直接使用,不再需要加!访问。
闭包的循环强引用
除了两个类实例的循环强引用,类实例和闭包也可能引起循环强引用,比如把闭包赋值给类的一个属性,而闭包中又通过self访问类的一个属性,这就引起了循环强引用,实例不会被销毁。
闭包占用列表
闭包占用列表(closuer capture list)可以解决闭包引起的循环强引用问题。
闭包调用类属性,必须加上
self.,不能直接通过属性名调用。
1234 lazy var someClosure: (Int, String) -> String = {[unowned self] (index: Int, stringToProcess: String) -> String in// closure body goes here}
定义占用列表,使用weak或unowned,视值是否能为nil而定。
上面的例子中,闭包的指定参数列表和返回类型可以通过上下文推断,所以省略。in放在占用列表之后。
这里使用了无主引用,闭包通过unowned self对类HTMLELement无主引用。
最后一句,赋值nil给paragraph,没有了闭包对它的强引用,可以销毁并调用析构函数。
转载请注明原作者和出处。
如果觉得这篇文章对您有帮助或启发,请随意打赏~
![]()
![]()
