博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Swift里你可能不知道的事儿(2)——处理对象reference cycle的三种方式
阅读量:7125 次
发布时间:2019-06-28

本文共 4647 字,大约阅读时间需要 15 分钟。

处理对象reference cycle的三种方式

我们在上一段视频里了解了reference cycle的成因。这次,我们来了解具体的解决方案。Reference cycle的解决方式,根据class member是否允许为nil,有着不同的处理方式。我们来分别看一下它们:

class member允许为nil时 - weak reference

在我们之前的例子里,Apartment和Person中引起reference cycle的数据成员都允许为nil,对于这种情况,我们可以像下面这样使用weak reference来解决reference cycle:

weak var name: Type

对于一个weak reference来说:

  • 当其指向类对象时,并不会添加类对象的引用计数;

  • 当其指向的类对象不存在时,ARC会自动把weak reference设置为nil;


“由于weak reference的特定,它只能被定义成var。”

——特别提示


了解了weak reference特性之后,我们可以把Apartment修改成这样:

class Apartment {    let unit: String    weak var tenant: Person?    // omit for simplicity...}var mars: Person? = Person(name: "Mars")var apt11: Apartment? = Apartment(unit: "11", owner: mars!)mars!.apartment = apt11apt11!.tenant = marsmars = nilapt11 = nil

之后,当我们把mars设置为nil的时候,Apartment和Person的关系就变成了这样:

bo-reading-mars-nil@2x.jpg

由于已经没有strong reference指向mars,于是mars就被ARC释放了。接下来,我们把apt11设置成nil:

bo-reading-apt11-nil@2x.jpg

这时,也没有任何strong reference指向apt11了,它也会被ARC释放。这就是当引起reference cycle的两个member允许为nil时,我们使用的解决方案。


“weak reference用于解决成员允许为nil的reference cycle。”

——特别提示


当有一个member不能为nil时 - unowned

来看另外一个应用场景。我们把Person改成下面这样:

class Person {    let name: String    var apartment: Apartment?    var property: Apartment?          // omit for simplicity...   }

由于不是每个人名下都有房产,因此property允许为nil,我们把它定义为Apartment optional。接下来,把Apartment的代码改成这样:

class Apartment {    let unit: String    weak var tenant: Person?    let owner: Person    init(unit: String, owner: Person) {        self.unit = unit        self.owner = owner        print("Apartment \(unit) is being initialized.")    }     // omit for simplicity...   }

由于每个Apartment一定会有房东,不能为nil,因此,我们把它定义为Person。

接下来,当我们再次创建mars和apt11的时候:

var mars: Person? = Person(name: "Mars")var apt11: Apartment? = Apartment(unit: "11", owner: mars!)mars!.apartment = apt11apt11!.tenant = marsmars = nilapt11 = nil

我们不难发现,添加的owner又引入了一个reference cycle。

bo-reading-owner-ref-cycle@2x.jpg

在上面这个图里,即使我们把mars和apt11设置为nil:

bo-reading-owner-ref-cycle1@2x.jpg

owner这个strong reference会让mars存活,进而apartment会让apt11存活。这就形成了一个新的reference cycl。

解决这种类型的reference cycle也很简单,我们把用下面的方式:

unowned let name: Type

把owner设置为一个unowned reference就可以了

class Apartment {    let unit: String    weak var tenant: Person?    unowned let owner: Person    // Omit for simplicity...}

和strong reference相比,unowned reference只有一个特别:不会引起对象引用计数的变化。因此,当我们把mars和apt11设置为nil时,对象的关系就会变成这样:

bo-reading-owner-ref-cycle2@2x.jpg

此时,已经没有strong reference引用mars,它会先被ARC释放掉,之后,引用apt11的apartment也不存在了,apt11也会被ARC清除。这就是reference cycle中一方不能为nil时的解决方案。


“unowned reference用于解决成员不允许为nil的reference cycle。”

——特别提示


当两个member都不允许为nil时

这种情况相对复杂一些,我们要让两个类成员互相配合处理这个问题。为了简单说明这种情况,我们重新定义两个类:

class Country {    let name: String        init(name: String) {        self.name = name    }}class City {    let name: String        init(name: String) {        self.name = name    }}

起初,它们很简单,Country代表国家,City代表城市,各自的init()函数用来构建对象。接下来,我们希望添加下面这样的语义:Country要有一个member表示首都,City要有一个country表示城市的归属。我们先做第一步的修改:

class Country {    let name: String    var capital: City    init(name: String) {        self.name = name    }}class City {    let name: String    let country: Country    init(name: String) {        self.name = name    }}

在我们的例子里,一个国家不可能没有首都,一个城市也不可能没有国家归属,因此,它们都只能是普通的类对象,而不能是一个Optional。接下来,我们来处理它们的初始化问题。首先,我们按照一般的方式处理City:

class City {    let name: String    let country: Country        init(name: String, country: Country) {        self.name = name        self.country = country    }}

然后,处理Country的init():

class Country {    let name: String    var capital: City        init(name: String, capitalName: String) {        self.name = name        // Syntax Error!!!        self.capital = City(name: capitalName, country: self)    }}

在Country的init()里,我们把正在创建的Country对象传递给了City的init(),这样做,只在语义上是正确的,语法上,Swift认为构建City时,Country还没有完成初始化(self.captical还没有确定的值),因此,它不允许我们在这个时候把self传递给Country。

bo-reading-uninit-self@2x.jpg

要想构建City时,让Swift认为Country已经构造完,唯一的做法就是captical有一个默认值nil。至此,对于Capital,我们有了两个看似冲突的需求:

  • 对Country的用户来说,不能让他们知道capital是一个optional;

  • 对Country的设计者来说,它必须像Optional一样有一个默认的nil;

而解决这种冲突唯一的办法,就是把capital定义为一个Implicitly Unwrapped Optional。

class Country {    let name: String    // Implicitly Unwrapped Optional    var capital: City! // default to nil        init(name: String, capitalName: String) {        self.name = name        // Syntax Error!!!        self.capital = City(name: capitalName, country: self)    }}

至此,两个彼此关联的类就可以正常的构建和初始化了。我们分别定义一个变量:

var cn: Country? = Country(name: "China", capitalName: "Beijing")var bj: City? = City(name: "Beijing", country: cn)cn = nilbj = nil

经历过之前的多个例子之后,我们很快就可以发现,当cn和bj为nil时,cn.captical和其内建的City仍旧会保持彼此“存活”在内存里,而解决这个问题的办法其实之间我们已经处理过了,把captical定义为unowned就可以了。


“unowned reference和implicitly unwrapped optional配合在一起,用于解决引起reference cycle的两个成员都不允许为nil的情况。”

——特别提示


接下来

在这段视频里,我们了解了处理类对象reference cycle的3种不同的方式。在下一段视频里,我们会发现,使用Closure会带来类似的问题。我们也会向大家介绍对应的处理办法

转载地址:http://haeel.baihongyu.com/

你可能感兴趣的文章
AngularJS—— 指令的复用
查看>>
FreeCMS有些功能出现乱码怎么办?
查看>>
AngularJS模块化开发实例
查看>>
python--django database grammar
查看>>
PHP内核探索:Zend虚拟机
查看>>
用 namspace 隔离 DHCP 服务 - 每天5分钟玩转 OpenStack(90)
查看>>
我的友情链接
查看>>
Linux下文件描述符
查看>>
单元测试系列之5:使用unitils测试Service层
查看>>
构建FTP服务器,基于SSL,基于虚拟用户
查看>>
centos 较准时间
查看>>
HBase 优化,columnFamily和qualifierColumn的设计原则
查看>>
java中Arraylist复制方法
查看>>
5月31日云栖精选夜读丨视频编辑,4k播放,3D游戏, 阿里云图形工作站,了解一下?...
查看>>
D3.js数据展现
查看>>
css3: calc 函数,动态调整数值
查看>>
SQL2008 降级 SQL2000
查看>>
C语言 三子棋 小游戏
查看>>
我的友情链接
查看>>
SELinux 简单介绍
查看>>