【Swift】Swift循环引用总结

作者: liqingbiubiu | 来源:发表于2016-05-26 11:33 被阅读968次

本文主要参考

1.循环强引用

由于Swift采用ARC(引用计数)来进行内存管理,开发者不必再去手动释放内存,但是如果代码产生了循环强引用,对象的引用计数无法归零,那么系统便无法自动释放对象,从而产生内存泄漏。

1.1 对象的循环强引用

我们假设两个类 类1、类2,这两个类中都有一个类类型的存储属性:

// 代码1.1
 class 类1{
      var 对象:类2!
      init(){
        print("对象1初始化!")
      }
    deinit{
      print("对象1释放!")
    }
  }
class 类2{
  var 对象:类1!
  init(){
      print("对象2初始化!")
  }
  deinit{
        print("对象2释放!")
  }
 }

然后我们对着两个类分别实例化两个对象,并让这两个对象彼此互相持有:

// 代码1.2
 var 对象1:类1?  = 类1()
 var 对象2:类2? =类2()
 对象1?.对象 = 对象2
 对象2?.对象 = 对象1

 对象1 = nil
 对象2 = nil

这样就会造成一个对象间的循环引用。最后,我们将这两个对象进行释放。发现控制台输出结果为:

对象1初始化!
对象2初始化!

而这两个对象并没有被释放,造成内存泄漏!

1.2 闭包的循环强引用

Swift中,闭包会自动捕捉变量,当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,或者闭包中调用了实例的某个方法。这两种情况都导致了闭包“捕获”self,从而产生了循环强引用。
例如:

// 代码1.3
class 类3{
 let name = "老王"
 lazy var 闭包:(Void -> Void)? = {
    var name1 = self.name
    var name2 = self.getName()
    print(name1)
    print(name2)
  }
  init(){
    print("对象3初始化!")
  }
  func getName()->String{
      return "老王"
  }
  deinit{
     print("对象3释放!")
  }
}

var 对象3:类3? = 类3()
对象3.闭包!()

对象3 = nil

在类3中,由于闭包中调用了实例的属性以及方法,从而导致闭包捕获了self,产生了循环强引用,造成了内存泄漏。

2.如何避免循环强引用

Swift中防止出现循环强引用主要有两种方法:
1. 代码显式调用,打破循环强引用
2. 使用weak、unowned关键字,用弱引用或无主引用打破循环强引用

2.1代码显式调用,打破循环强引用

这种方法需要开发者在使用完对象或闭包后,主动释放所持有的其他对象的强引用,从而打破循环,让系统回收内存。
例如,我们可以将代码1.2修改为:

// 代码2.1
 var 对象1:类1?  = 类1()
 var 对象2:类2? =类2()
 对象1?.对象 = 对象2
 对象2?.对象 = 对象1

//主动释放所持有的强引用对象
 对象1?.对象 = nil
 对象2?.对象 = nil
 对象1 = nil
 对象2 = nil

对于代码1.3可以主动释放对象中的闭包:

//代码2.2
var 对象3:类3? = 类3()
对象3?.闭包!()

对象3?.闭包 = nil
对象3 = nil

这样就可以避免内存泄漏,但是这种方式有悖于ARC设计的初衷,不推荐使用。

2.2使用weak、unowned关键字,用弱引用或无主引用打破循环强引用

2.2.1 weak、unowned关键字

Swift中,声明属性或者变量时,在前面加上weak关键字表明这是一个弱引用,弱引用不会对其引用的实例保持强引用,不会增加引用对象的引用计数,因而不会阻止 ARC 销毁被引用的实例。因为弱引用可以没有值,因此你必须将每一个弱引用声明为可选类型。弱引用不会保持所引用的实例,即使引用存在,实例也有可能被销毁。因此,ARC 会在引用的实例被销毁后自动将其赋值为nil。你可以像其他可选值一样,检查弱引用的值是否存在,你将永远不会访问已销毁的实例的引用。

与weak关键字类似,在属性或变量前面加上unowned关键字表明这是一个无主引用,无主引用也不会对其引用的实例保持强引用,从而避免出现循环强引用。然而与weak关键字不同的是,即使引用的对象已经被销毁,无主引用仍然存在,这时候如果强制使用引用会导致程序的崩溃。对于这两个关键字的选择,建议如果能够确保在引用的使用过程中对象始终存在,则尽量使用unowned,否则建议使用更为安全的weak关键字。

2.2.2 weak、unowned关键字在属性或变量上的使用

weak、unowned关键字在属性或变量上的使用比较简单,代码如下:

// 代码2.3
//weak关键字
class 类1{
  weak var 对象:类2!
  init(){
     print("对象1 初始化!")
  }
  deinit{
    print("对象1 释放!")
  }
 }
// unowned关键字
class 类1{
  unowned var 对象:类2
  init(对象2:类2){
      对象 = 对象2
      print("对象1 初始化!")
  }
  deinit{
      print("对象1 释放!")
  }  
}

2.2.3 weak、unowned关键字在闭包中的使用

weak、unowned关键字在闭包中的使用主要有两种方式,首先便是在闭包定义之前先用weak、unowned关键字引用对象,从而让闭包捕获弱引用或无主引用对象,从而保证不会出现循环强引用:

class 类3{
  let name = "老王"
  var closure:(Void -> Void)?
  init(){
    print("对象3初始化!")
    unowned let myself = self
    closure = {
        //捕获myself无主引用
        var name = myself.name
        name = myself.getName()
        print(name)
    }
  }
 }

这种方式比较麻烦,不推荐使用。Swift提供了另外一个简便的方式:自定义捕获列表。开发者在定义闭包的时候可以首先定义需要捕获变量类型为weak或unowned,从而防止出现循环强引用:

class 类3{
  let name = "老王"
  lazy var closure:(Void -> Void)? = {
  //自定义捕获列表
   [unowned self] in
      var name = self.name
      name = self.getName()
      print(name)
   }
 }

这样,闭包中再使用self的时候便不会出现强引用了。

注:文中如有任何错误,请各位批评指正!

相关文章

  • Swift--内存管理

    Swift内存管理概述 强引用循环 打破强引用循环 闭包中的强引用循环 Swift内存管理概述 Swift中的AR...

  • 【Swift】Swift循环引用总结

    本文主要参考The Swift Programming Language 中文版——闭包The Swift Pro...

  • Swift与OC真正去理解Block解决循环引用的技巧

    Swift与OC真正去理解Block解决循环引用的技巧 Swift与OC真正去理解Block解决循环引用的技巧

  • 解决swift Block内的循环引用

    解决swift Block内的循环引用

  • Swift 循环引用

    Swift 循环引用 [TOC] 前言 本本主要研究的是Swift中闭包捕获外部变量时产生的循环引用。全部示例是在...

  • 2019-12-04

    swift 闭包循环引用产生以及处理

  • 关于Swift闭包的循环引用问题

    由Swift ARC内存管理以及循环引用可以得出Swift闭包中的循环引用问题,然后我写了另外一个demo去验证这...

  • Swift 小知识点

    字符串的截取 字符串转为URL swift命令行模式 swift循环引用

  • Day2

    1 单方向的引用不会产生循环引用。循环引用:闭包引用了self,self引用了闭包。Swift推荐使用解决循环引用...

  • swift循环引用

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

网友评论

    本文标题:【Swift】Swift循环引用总结

    本文链接:https://www.haomeiwen.com/subject/fbutdttx.html