转载

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

昨晚跟 张瑞鹏 谈起了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

文档中的一个例子如下:

如下定义了 CustomerCreditCard 两个类,用于表示银行的客户和客户的信用卡。这两个类中,每一个都将另一个类的实例作为自身的属性。在这个数据模型中,有一点与其他循环强引用的例子不同:一个客户可能有或者没有信用卡,但是一张信用卡总是对应着一个确定的客户。为了表示这种关系, 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 。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解析可选属性。

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

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! )的方式,将 CountrycapitalCity 属性声明为隐式解析可选类型的属性。这表示像其他可选类型一样, capitalCity 属性的默认值为 nil ,但是不需要展开它的值就能访问它。

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

以上的意义在于你可以通过一条语句同时创建 CountryCity 的实例,而不产生循环强引用,并且 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.nameself.tex t ),因此闭包捕获了 self ,这意味着闭包又反过来持有了 HTMLElement 实例的强引用。这样两个对象就产生了循环强引用。

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

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

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

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

Note: Swift 有如下要求:只要是在闭包内使用的成员,就要用 self.someProperty 或者 self.someMethod() (而不只是 somePropertysomeMethod )。这提醒你可能会一不小心就捕获了 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 } 
原文  http://iJack.pw/2016/03/02/ARC-in-Swift/
正文到此结束
Loading...