iOS-SwiftSwift学习Swift

扒一扒swift中的unowned和weak上

2016-08-28  本文已影响1376人  2e919d99a871

最近项目中踩到了Closure使用unowned和weak修饰self对象避免循环引用所带来的坑,之前不是太了解,Closure里面会用到self的地方,一般都是这样处理

{[unowned self] in }

但是在一个网络请求的Closure回调里面,这样写总是会令程序crash掉,后来发现在这个Closure执行之前,self已经被释放了,在我们老大查阅了许多资料后把代码改成这样就ok了

{[weak self] in 
  if let weSelf = self{
      执行代码
}
}

最近跟着泊学网(被誉为最良心的swift视频教学网站)的Mars讲师学习了关于swift中Reference cycle引起的内存泄漏方面的知识,总结一下,留着学习!~
那么,一起来扒一扒swift中unwoned和weak之间的恩恩怨怨吧~

首先,想要弄明白unwoned和weak的区别,就一定要先明白他们能做什么,所以,我们就从Reference cycle引起的内存泄露说起吧!
大家都知道,swift会使用ARC(Automatic Reference Counting)帮我们打理内存,但是在有些时候,尤其是对象间存在互相引用的时候,就会由于reference cycle导致内存无法释放,而我们也需要时刻保持清醒,来避免内存泄露。那么

ARC是如何工作的呢?

Swift使用“引用计数(reference count)”来管理类对象的生命周期,避免类对象在“仍被使用”的时候被意外释放。为了观察reference count、对象构建和对象释放之间的关系,我们先来定义一个类:

class Person { 
let name: String
init(name: String) { 
      self.name = name print("\(name) is being initialized.")
   }
 deinit { 
      print("\(name) is being deinitialized.") 
   }
}

接下来,我们观察下面代码的的执行过程:

var ref1: Person?
var ref2: Person?
// Mars is being initialized.
// count = 1
ref1 = Person(name: "Mars")
// count = 2
ref2 = ref1
// count = 1
ref2 = nil
// count = 0
// Mars is being deinitialized.
ref1 = nil

注意: “和带有Garbage Collection的语言不同,当一个对象的reference count为0时,Swift会立即删除该对象。”

那么Reference cycle(循环引用)又是怎么发生的呢?

对于Person这样的单个对象,ARC可以很好的默默为我们工作。但是,当不同类对象之间存在相互引用时
,指向彼此的strong reference就会导致reference cycle。ARC无法释放它们之中的任何一个。来看一个例子,我们给Person添加一个Apartment:

class Person {
 let name: String 
 var apartment: Apartment?
 init(name: String) {
     self.name = name print("\(name) is being initialized.")
 } 
deinit { 
    print("\(name) is being deinitialized.") 
  }
}

class Apartment { 
 let unit: String
 var tenant: Person?
 init(unit: String) { 
    self.unit = unit print("Apartment \(unit) is being initialized.")
 }
 deinit { 
    print("Apartment \(unit) is being deinitialized.") 
  }
}

由于Person不一定会租Apartment(公寓),Apartment(公寓)也不一定有房客,因此,Person.apartment和Apartment.tenant都是一个Optional,它们可以为nil。
接下来,我们分别创建一个Person和Apartment对象,然后把变量设置为nil,就可以看到Person和Apartment被构建和销毁了。

// Mars is being initialized
// count = 1
var mars: Person? = Person(name: "Mars")
// Apartment 11 is being initialized
// count = 1
var apt11: Apartment? = Apartment(unit: "11")
// count = 0
// Mars is being deinitializedmars = nil
// count = 0
// apartment is being deinitializedapt11 = nil
image

接下来,如果我们让mars和apartment分别指向彼此:

// ... Create mars and apartment
// apartment.count = 2
mars!.apartment = apt11
// mars.count = 2
apt11!.tenant = mars
// ... Set mars and apartment to nil
mars = nil
apt11 = nil
image

这时,尽管我们把mars和apt11设置为nil,Person和Apartmetn的deinit也不会被调用了。因为它们的两个member(apartment和tenant)是一个strong reference,指向了彼此,让对象仍旧“存活”在内存里。但是,mars和apt11已经被设置成nil,我们也已经无能为力了。这就是类对象之间的reference cycle。

三种Reference cycle的解决方案

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

weak var name: Type

对于一个weak reference来说:

注意: * “由于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 = apt11
apt11!.tenant = mars
mars = nil
apt11 = nil

mars设置为nil的时候,Apartment和Person的关系就变成了这样:


image

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


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

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

2. 当有一个member不能为nil时
来看另外一个应用场景。我们把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 = apt11
apt11!.tenant = mars
mars = nil
apt11 = nil
image

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


image

owner这个strong reference会让mars存活,进而apartment会让apt11存活。这就形成了一个新的reference cycl。
解决办法很简单,我们把owner设置为unowned就ok了!

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

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


image

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

3.当两个member都不允许为nil的时候
一个国家不可能没有首都,一个城市也不可能没有国家归属,因此,它们都只能是普通的类对象,而不能是一个Optional。接下来,我们来处理它们的初始化问题。首先,我们按照一般的方式处理City:

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

然后,处理Country

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

我们分别定义一个变量:

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

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

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

上篇完

上一篇下一篇

猜你喜欢

热点阅读