文档章节

Swift中如何解决循环引用问题?

 天使爱美
发布于 2016/11/04 16:39
字数 2231
阅读 14
收藏 0

 

和OC一样,swift开发中也是使用自动引用计数ARC(Auto Reference Counteting)来自动管理内存的,所以我们不需要过多考虑内存管理.当某个类实例不需要用到的时候,ARC会自动释放其占用的内存.

  ARC仅仅能对类的实例做内存管理,也就是只能针对引用类型.结构体和枚举都是值类型,不能通过引用的方式来传递和存储,所以ARC也就不能对它们进行内存管理.

  什么情况下会导致循环引用

  在swift中,每创建一个实例,ARC都会为其分配一块内存空间,而在不使用的时候,ARC会释放和收回那个实例所占的内存空间,该实例的属性和方法也就不能够被访问,如果要访问就会导致程序崩溃.

  怎么确定实例不被使用了?ARC会自动追踪实例被多少常量和变量引用.每追踪到一个,自动引用计数会加一,减少一个引用自动引用计数会减一,如果当自动引用计数变为0的时候,ARC就会收回内存,销毁实例.

  下面是一个自动引用计数的实例:

  class Person {

  let name: String

  init(name: String) {

  self.name = name

  print("\(name)正在被初始化")

  }

  deinit {

  print("\(name)即将被销毁") // person3 = nil时打印

  }

  }var person1: Person? // 可选类型的变量,方便置空var person2: Person?var person3: Person?

  person1 = Person(name: "Dariel") //创建Person实例并与person1建立了强引用

  person2 = person1 // 只要有一个强引用在,实例就能不被销毁

  person3 = person1 // 目前该实例共有三个强引用

  person1 = nil

  person2 = nil // 因为还有一个强引用,实例不会被销毁

  person3 = nil // 最后一个强引用被断开,ARC会销毁该实例

  上面的例子中创建的 Person 实例最后引用计数变为了0被销毁了,但现实世界并不会一直都这么美好, ARC这种机制也有自己的局限性,请看下面的例子:

  class People {

  let name: String

  init(name: String) { self.name = name }

  var apartment: Apartment? // 人住的公寓属性deinit {

  print("People被销毁")

  }

  }

  class Apartment {

  let unit: String

  init(unit: String) { self.unit = unit }

  var tenant: People? // 公寓中的人的属性

  deinit {

  print("Apartment被销毁")

  }

  }

  var people1: People? = People(name: "Dariel") // 定义两个实例变量var apartment1: Apartment? = Apartment(unit: "4A")

  people1!.apartment = apartment1 // 两者相互引用

  apartment1?.tenant = people1 // 而且彼此都是强引用

  people1 = nil

  apartment1 = nil // 两个引用都置为nil了,但实例并没有销毁

  这一次直接创建了两个实例, People 中有一个 Apartment 的属性, Apartment 中又有一个 People 属性,当我们创建了两个实例后分别给实例中的这两个属性赋完值,又将两个可选变量赋值为nil,并没有看到两个实例被销毁的打印信息( deinit 函数会在实例被销毁的时候打印).

  也就是说ARC并没有销毁两个对象.那么问题在哪里?

  当两个可选变量被赋值为nil时,ARC并没有觉得这两个实例已经不在使用了.因为两个实例的相互赋值时使得各自的引用计数+1,这也就是发生循环引用了.

  怎么解决循环引用

  1. 如果产生循环引用的两个属性都允许为nil,这种情况适合用弱引用来解决

  随便哪一个可选类型的属性前面都可以加 weak ,但记住只要加一个就行了.

  话不多说上代码:

  class OtherPeople {

  let name: String

  init(name: String) { self.name = name }

  var apartment: OtherApartment? // 人住的公寓属性

  deinit { print("\(name)被销毁") }

  }

  class OtherApartment {

  let unit: String

  init(unit: String) { self.unit = unit }

  weak var tenant: OtherPeople? // 加一个weak关键字,表示该变量为弱引用

  deinit { print("\(unit)被销毁") }

  }

  var otherPeople1: OtherPeople? = OtherPeople(name: "Dariel") // 定义两个实例变量var otherApartment1: OtherApartment? = OtherApartment(unit: "4A")

  otherPeople1!.apartment = otherApartment1 // 两者相互引用

  otherApartment1?.tenant = otherPeople1 // 但tenant是弱引用

  otherPeople1 = nil

  otherApartment1 = nil // 实例被销毁,deinit中都会打印销毁的信息

  在 OtherPeople 和 OtherApartment 两个类中,相互引用的两个属性都为可选类型,那么可以在一个属性的前面添加 weak 关键字,使该变量变为弱引用.

  对的,没错,这个weak还是以前OC里面的那个weak.

  2. 如果产生循环引用的两个属性一个允许为nil,另一个不允许为nil,这种情况适合用无主引用来解决

  只能在不能为nil的那个属性前面加 unowned 关键字,就是说 unowned 设置以后即使它原来引用的内容已经被释放了,它仍然会保持对被已经释放了的对象的一个 "无效的" 引用,它不能是 Optional 值,也不会被指向 nil。如果尝试去调用这个引用的方法或者访问成员属性的话,程序就会崩溃.

  无主引用的例子:

  class Dog {

  let name: String

  var food: Food?

  init(name: String) {

  self.name = name

  }

  deinit { print("\(name)被销毁") }

  }class Food {

  let number: Int

  unowned var owner: Dog // owner是一个无主引用

  init(number: Int, owner: Dog) {

  self.number = number

  self.owner = owner

  }

  deinit { print("食物被销毁") }

  }

  var dog1: Dog? = Dog(name: "Kate")

  dog1?.food = Food(number: 6, owner: dog1!) // dog强引用food,而food对dog是无主引用

  dog1 = nil // 这样就可以同时销毁两个实例了

  Dog 的 food 属性可以为空,而 Food 的 owner 属性不能为空,我们把 owner 设为无主引用.

  3. 如果产生循环引用的两个属性都必须有值,不能为nil,这种情况适合一个类使用无主属性,另一个类使用隐式解析可选类型

  隐式解析可选类型: 类似可选类型,默认值可以设置为nil

  两个属性一个在类型后面加 ! 设置为隐式解析可选类型,另一个在属性前面加unowned 关键字,设置为无主属性.

  class Country {

  let name: String

  var capitalCity: City! // 初始化完成后可以当非可选类型使用

  init(name: String, capitalName: String) {

  self.name = name

  self.capitalCity = City(name: capitalName, country: self)

  }

  deinit { print("Country实例被销毁") }

  }

  class City {

  let name: String

  unowned let country: Country

  init(name: String, country: Country) {

  self.name = name

  self.country = country

  }

  deinit { print("City实例被销毁") }

  }

  // 这样一条语句就能够创建两个实例var country: Country? = Country(name: "China", capitalName: "HangZhou")print(country!.name) // Chinaprint(country!.capitalCity.name) // HangZhou

  country = nil // 同时销毁两个实例

  Country 的 City 属性后加!为隐式解析可选属性,类似可选类型,capitalCity属性的默认值为nil,一旦在 Country 的构造函数中给 name 属性赋完值后, Country 的整个初始化过程就完成了,就能将 self 作为参数传递给 City 的构造函数了.

  总而言之,就是一条语句创建两个实例,还不产生循环引用.

  闭包也是引用类型,怎么解决闭包的循环强引用

  闭包中对任何其他元素的引用都是会被闭包自动持有的。如果我们在闭包中写了self 这样的东西的话,那我们其实也就在闭包内持有了当前的对象。这里就出现了一个在实际开发中比较隐蔽的陷阱:如果当前的实例直接或者间接地对这个闭包又有引用的话,就形成了一个 self -> 闭包 -> self 的循环引用

  怎样避免这种情况呢?

  可以在闭包开始的时候添加一个标注,来表示这个闭包内的某些要素应该以何种特定的方式来使用

  看例子:

  class Element {

  let name: String

  let text: String?

  lazy var group:() -> String = { // 相当于一个没有参数返回string的函数

  [unowned selfin // 定义捕获列表,将self变为无主引用

  if let text = self.text { // 解包

  return "\(self.name), \(text)"

  }else {

  return "\(self.name)"

  }

  }

  init(name: String, text: String? = nil) {

  self.name = name

  self.text = text

  }

  deinit { print("\(name)被销毁") }

  }

  var element1: Element? = Element(name: "Alex", text: "Hello")print(element1!.group()) // Alex, Hello,闭包与实例相互引用

  element1 = nil // self为无主引用,实例能被销毁

  在闭包中定义一个捕获列表 [unowned self] ,将self变为无主引用.这样就能够在避免产生循环强引用了.

  小结

  解决循环引用的三种方法,这三种方法的产生主要还是swift中要考虑属性为空的情况.

  · 如果产生循环引用的两个属性都允许为nil,这种情况适合用弱引用来解决.

  · 如果产生循环引用的两个属性一个允许为nil,另一个不允许为nil,这种情况适合用无主引用来解决.

  · 如果产生循环引用的两个属性都必须有值,不能为nil,这种情况适合一个类使用无主属性,另一个类使用隐式解析可选类型 .

 

文章来源:简书

© 著作权归作者所有

粉丝 1
博文 36
码字总数 59694
作品 0
朝阳
私信 提问
Swift专题讲解十六——ARC在Swift中的应用

Swift专题讲解十六——ARC在Swift中的应用 一、引言 ARC(自动引用计数)是Objective-C和Swift中用于解决内存管理问题的方案。在学习Objective-C编程时经常会学习到一个关于ARC的例子:在一个...

珲少
2016/05/20
1K
1
Swift中解决引用循环之Unowned 与 Weak的选择

Swift的内存管理机制与Object-C一样,都是采用了自动内存管理 -- ARC。那么这样就不得不想到老生常谈的一个问题——引用循环。 Object-C中,我们习惯使用weak关键词来打破这样的循环,使循环...

BennyLoo
2017/11/22
0
0
【自问自答】关于 Swift 的几个疑问

感觉自己给自己释疑,也是一个极为有趣的过程。这次,我还新增了“猜想”一栏,来尝试回答一些暂时没有足够资料支撑的问题。 Swift 版本是:4.0.3。不同版本的 Swift,可能无法复现问题。 个...

ios122
2018/01/06
0
0
自动引用计数

自动引用计数 工作机制 Swift和OC一样,采用自动引用计数来管理内容 当有一个强引用指向某一个动向时,该对象的引用计数会自动+1 当该强引用消失时,引用计数会自动-1 当引用计数为0时,该对象会...

别情花如依丶
2016/12/22
5
0
Swift/Objective-C-使用Cocoapods创建/管理私有库(高级用法)

接着上篇文章"Swift/Objective-C-使用Cocoapods创建/管理私有库(初中级用法)"的探索之路。 另外两篇文章: Swift/Objective-C-使用Cocoapods创建/管理公共库 Swift/Objective-C-使用Cocoa...

sky_storming
03/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

用原生js对表格排序

本文转载于:专业的前端网站➸用原生js对表格排序 阿里的模拟笔试题,当时时间有限没写出来,其实是因为自己对原生dom操作不熟悉,这里补一下。 题目的大意是有一个表格,如代码所示 <table>...

前端老手
20分钟前
3
0
IT兄弟连 HTML5教程 HTML5表单 HTML5新增表单元素

HTML5有一些新的表单元素:<datalist>、<keygen>、<output>。不是所有的浏览器都支持HTML5新的表单元素,但即使浏览器不支持该表单属性,仍然可以显示为常规的表单元素。 1 <datalist>元素 ...

老码农的一亩三分地
21分钟前
3
0
【朝花夕拾】Android自定义View篇之(一)View绘制流程

https://www.cnblogs.com/andy-songwei/p/10955062.html

shzwork
23分钟前
4
0
Qt编写自定义控件70-扁平化flatui

一、前言 对于现在做前端开发人员来说,FlatUI肯定不陌生,最近几年扁平化的设计越来越流行,大概由于现在PC端和移动端的设备的分辨率越来越高,扁平化反而看起来更让人愉悦,而通过渐变色产...

飞扬青云
33分钟前
2
0
教你玩转Linux—添加批量用户

添加和删除用户对每位Linux系统管理员都是轻而易举的事,比较棘手的是如果要添加几十个、上百个甚至上千个用户时,我们不太可能还使用useradd一个一个地添加,必然要找一种简便的创建大量用户...

Linux就该这么学
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部