详解Swift的内存管理

这篇文章主要介绍了Swift的内存管理,对内存管理感兴趣的同学,可以参考下

内存管理

和OC一样, 在Swift中也是采用基于引用计数的ARC内存管理方案(针对堆空间的内存管理)

在Swift的ARC中有三种引用

  • 强引用(strong reference):默认情况下,代码中涉及到的引用都是强引用
  • 弱引用(weak reference):通过weak定义弱引用
  • 无主引用(unowned reference):通过unowned定义无主引用

weak

弱引用(weak reference):通过weak定义弱引用必须是可选类型的var,因为实例销毁后,ARC会自动将弱引用设置为nilARC自动给弱引用设置nil时,不会触发属性观察

在介绍weak弱引用之前, 先看一下下面一段代码

 class Animal { deinit { print("Animal deinit") } } func test() { let animal = Animal() } print("will deinit") test() print("did deinit")

上面这段代码中在test函数调用结束之后, 该作用的内存就会被回收,animal对象自然就会被销毁, 毫无疑问上面的输出结果应该是

will deinit

Animal deinit

did deinit

同样下面这段代码, 同样也是在a1对象被置为nil的时候内存会被回收, 对象就会被销毁

 var a1: Animal? = Animal() print("will deinit") a1 = nil print("did deinit")

下面是一个被weak修饰的弱引用对象,

我们都知道, 被weak修饰的弱引用对象, 在对象销毁的时候, 会被自动置为nil

所以被weak修饰的弱引用对象必须是可选类型的var, 两个条件缺一不可

 weak var a2: Animal? = Animal() // 以下两种方式都会报错的 weak var a2: Animal = Animal() weak let a2: Animal? = Animal()

unowned无主引用(unowned reference):通过unowned定义无主引用

不会产生强引用,实例销毁后仍然存储着实例的内存地址(类似于OC中的unsafe_unretained)

试图在实例销毁后访问无主引用,会产生运行时错误(如下野指针)

Fatal error: Attempted to read an unowned reference but object 0x0 was already deallocate

需要注意的是

weak、unowned只能用在类实例上面, 如下所示

 // 该协议表示只能被类遵守, AnyObject代表所有的类实例 protocol Liveable: AnyObject {} class Person {} weak var p0: Person? weak var p1: AnyObject? // 所有能遵循Liveable协议的肯定都是类 weak var p2: Liveable? unowned var p10: Person? unowned var p11: AnyObject? unowned var p12: Liveable?

循环引用

  • weak、unowned都能解决循环引用的问题,unowned要比weak少一些性能消耗
  • 在生命周期中可能会变为nil的使用weak
  • 初始化赋值后再也不会变为nil的使用unowne
  • 说道循环引用就自然想到了闭包

闭包的循环引用

闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行了retain操作), 看一下下面的代码中deinit会被调用吗?

 class Person { var fn: (() -> ())? func run() { print("run") } deinit { print("deinit") } } func test() { let p = Person() p.fn = { p.run() } } test()

上面代码中,p对象强引用着fn闭包,fn闭包也强引用着p对象, 自然就造成了循环引用问题

最后没有任何输出结果, 我们看一下上述代码的汇编执行过程

从上面汇编代码可以看出, 整个过程经历了

一次init引用计数为: 1

一次retain引用计数会加(1), 结果为: 2

一次release引用计数会减(1), 结果为: 1

那么最后的引用计数就是1, 所以p对象肯定没有被释放

下面是使用解决循环引用的情况

在闭包表达式的捕获列表里, 声明weak或unowned引用,用以解决循环引用问题

 // 使用weak func test() { let p = Person() p.fn = { [weak p] in p?.run() } } // 使用unowned func test() { let p = Person() p.fn = { [unowned p] in p.run() } }

上述两种方式都可以解决循环引用的问题, 运行后就发现Person对象调用了deinit这里我们再看一下汇编代码如下, 从下面汇编代码中可以很明显看到, 引用计数最后为0, 对象被释放

下面这段代码其实是等价的

 func test() { let p = Person() p.fn = { [unowned p] in p.run() } } // 和上面等价代码 func test() { let p = Person() p.fn = { [unowned ownedP = p, weak weakP = p] in ownedP.run() // weakP?.run() } }

特别注意点

这里要区分捕获列表和参数列表, 下面看看fn有参数的情况下

 class Person { var fn: ((Int) -> ())? func run() { print("run") } deinit { print("deinit") } } func test() { let p = Person() p.fn = { (num) in print("num = \(num)") } }

那么闭包的参数列表和捕获列表同时存在的情况如下代码所示

 func test() { let p = Person() p.fn = { [weak p](num) in print("num = \(num)") p?.run() } }

self的循环引用

如果想在引用闭包的同时引用self, 这个闭包必须是lazy的

因为实例在初始化完毕之后才能引用self

 class Person { lazy var fn: (() -> ()) = { self.run

以上就是详解Swift的内存管理的详细内容,更多请关注0133技术站其它相关文章!

赞(0) 打赏
未经允许不得转载:0133技术站首页 » 其他教程