浅谈iOS中的内存管理(四):Swift中的ARC

ticklishelephant 发布于8月前 阅读7664次
0 条评论

昨晚跟 张瑞鹏 谈起了Rust语言,其实一直对Rust语言充满了敬畏。Google了一下Rust vs Swift似乎也没什么靠谱的回答,可能二者目前区别不是很大。不过 Quora 上的一个回答引发了我的兴趣。

这里撇开Swift中的即时编译、交互式编程,只关注内存管理这方面。( Chris Lattner 在他的博客中也提到这两者特性只是他个人的一时兴起。)

Troy Harvey 说Rust的所有权系统(Ownership system)可以100%的防止内存泄露的发生,而Swift中使用的自动引用计数 98%可以杜绝内存泄露的发生(由于存在循环强引用)。

这里就来探索Swift中的ARC与此前OC中的ARC发生了哪些改变?这里把重点放在如何在Swift中解决循环强引用。

无主引用 (unowned reference)

在之前的三篇文章中,可以得知,在OC中 weak 可以用来破除两个强引用的循环引用。在Swift中,则又多了一个无主引用(unowned reference)。

弱引用(weak reference)和无主引用(unowned reference)都允许循环引用中的某一个实例引用另外一个实例而不保持强引用,这样实例就能够互相引用而不会产生循环强引用的副作用。

那么弱引用(weak reference)和无主引用(unowned reference)在Swift中又有什么区别呢?一般来说,对于在生命周期中会变为 nil 的实例使用弱引用(weak reference)。对于在初始化赋值后再也不会变为 nil 的实例,使用无主引用(unowned reference)。

无主引用永远都是有值的,这与弱引用不同。因此,无主引用总是被定义为非可选类型。可以在声明前加入 unowned 关键字以表示一个无主引用。

由于无主引用是非可选类型,所以不需要在使用时将其展开。无主引用总是可以被直接访问。不过ARC无法在实例被销毁后将无主引用设为 nil ,因为非可选类型不允许被赋值为 nil 。

文档中的一个例子如下:

如下定义了 Customer 和 CreditCard 两个类,用于表示银行的客户和客户的信用卡。这两个类中,每一个都将另一个类的实例作为自身的属性。在这个数据模型中,有一点与其他循环强引用的例子不同:一个客户可能有或者没有信用卡,但是一张信用卡总是对应着一个确定的客户。为了表示这种关系, Customer 类有一个可选类型的 card 属性,但是 CreditCard 类有一个非可选类型的 customer 属性。

由于信用卡总是对应着一个客户,因此将 customer 属性定义为无主引用,

classCustomer{
letname:String
varcard:CreditCard?
init(name:String) {
self.name = name
 }
deinit{
print("\(name)is being deinitialized.")
 }
}

classCreditCard{
letnumber:UInt64
unownedletcustomer:Customer
init(number:UInt64, customer:Customer) {
self.number = number
self.customer = customer
 }
deinit{
print("Card #\(number)is being deinitialized.")
 }
}

在这个模型中, Customer 实例持有对 CreditCard 实例的强引用, 而 CreditCard 实例持有对 Customer 实例的无主引用。

还有可能存在另一种场景:在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为 nil 。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解析可选属性。

如下定义了 Country 和 City 两个类,在这个模型中,每个城市必须属于一个国家,每个国家必须有一个首都城市。

classCountry{
letname:String
varcapitalCity:City!
init(name:String, capitalName:String){
self.name = name
self.capitalCity =City(name: capitalName, country:self)
 }
}

classCity{
letname:String
unownedletcountry:Country
init(name:String, country:Country) {
self.name = name
self.country = country
 }
}

Country 的构造函数调用了 City 的构造函数。然而只有 Country 的实例完全初始化完后, Country 的构造函

数才能把 self 传给 City 的构造函数。为了满足这种需求,通过在类型结尾处加上感叹号( City! )的方式,将 Country 的 capitalCity 属性声明为隐式解析可选类型的属性。这表示像其他可选类型一样, capitalCity 属性的默认值为 nil ,但是不需要展开它的值就能访问它。

由于 capitalCity 默认值为 nil ,一旦 Country 的实例在构造函数中给 name 属性赋值后,整个初始化过程就完成了。这代表一旦 name 属性被赋值后, Country 的构造函数就能引用并传递隐式的 self 。 Country 的构造函数在赋值 capitalCity 时,就能将 self 作为参数传递给 City 的构造函数。

以上的意义在于你可以通过一条语句同时创建 Country 和 City 的实例,而不产生循环强引用,并且 capitalCity 的属性能被直接访问,而不需要通过感叹号来展开它的可选值。

闭包引起的循环引用

当一个闭包被赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时,也会出现循环引用。

如下的例子用来展示一个由闭包引起的循环引用的发生:

classHTMLElement{
letname:String
lettext:String?
 lazy varasHTML:Void->String= {
iflettext =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.")
 }
}

这个例子中闭包在其闭包体内使用了 self (引用了 self.name 和 self.tex t ),因此闭包捕获了 self ,这意味着闭包又反过来持有了 HTMLElement 实例的强引用。这样两个对象就产生了循环强引用。

Note: 虽然闭包多次使用了 self ,但它只捕获 HTMLElement 实例的一个强引用。

Swift 中对此提供了一中优雅的解决方案叫做闭包捕获列表( closuer capture list )。

定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。

捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,可以声明每个捕获的引用为弱引用或无主引用。

Note: Swift 有如下要求:只要是在闭包内使用的成员,就要用 self.someProperty 或者 self.someMethod() (而不只是 someProperty 或 someMethod )。这提醒你可能会一不小心就捕获了 self 。

于是,我们可以改造此前的 asHTML 闭包。

classHTMLElement{
letname:String
lettext:String?
 lazy varasHTML:Void->String= {
 [unownedself]in
iflettext =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.")
 }
}

捕获列表常见的语法如下:

lazy varsomeClosure:(Int, String)->String = {
 [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String)->Stringin
//closure body goes here
}

如果闭包没有指明参数列表或者返回类型,则会通过上下文推断,那么可以把捕获列表和关键字 in 放在闭包最开始的地方:

lazy varsomeClosure:Void->String= {
 [unownedself,weakdelegate =self.delegate!]in
// closure body goes here
}
需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。