内存管理

来源:互联网 发布:淘宝直播难申请成功 编辑:程序博客网 时间:2024/06/11 19:21

内存管理 - 引用计数

1.内存管理原理:引用计数

引用计数发展时间轴:

苹果在 2011 年的时候,在 WWDC 大会上提出了自动的引用计数(ARC)。ARC 背后的原理是依赖编译器的静态分析能力,通过在编译时找出合理的插入引用计数管理代码,从而彻底解放程序员。

直到 2013 年左右,苹果认为 ARC 技术足够成熟,直接将 macOS(当时叫 OS X)上的垃圾回收机制废弃,从而使得 ARC 迅速被接受。

2014 年的 WWDC 大会上,苹果推出了 Swift 语言,而该语言仍然使用 ARC 技术,作为其内存管理方式。

虽然 ARC 帮我们解决了引用计数的大部分问题,但是还有一部分工作需要我们自己来处理。内存问题可能会导致内存泄漏,使得应用运行缓慢或者被系统终止进程。

所以要想处理好内存问题,我们首先需要理解引用计数这种内存管理方式。

什么是引用计数

引用计数(Reference Count)是一个简单而有效的管理对象生命周期的方式。当我们创建一个新对象的时候,它的引用计数为 1,当有一个新的指针指向这个对象时,我们将其引用计数加 1,当某个指针不再指向这个对象是,我们将其引用计数减 1,当对象的引用计数变为 0 时,说明这个对象不再被任何指针指向了,这个时候我们就可以将对象销毁,回收内存。由于引用计数简单有效

为了更形象一些,我们再来看一段 Objective-C 的代码。新建一个工程,因为现在默认的工程都开启了自动的引用计数 ARC(Automatic Reference Count),我们先修改工程设置,给 AppDelegate.m 加上 -fno-objc-arc 的编译参数(如下图所示),这个参数可以启用手工管理引用计数的模式。

WechatIMG163

为什么采用了引用计数的方式来管理内存呢?

上述例子比较简单,可能看不出引用计数的真正的用处,因为该对象的生命周期在一个函数内,即时不修改它的引用计数的也是可以的,只需要在函数返回前将该对象销毁即可。

引用计数真正派上用场的场景是用于对象之间传递和共享数据。我们举一个具体的例子:

场景1:

按照假设有两个Cotnroller,分别为ControllerA、ControllerB,ControllerA中将对象M传递给ControllerB,在没有引用计数的情况下,一般内存管理的原则是 “谁申请谁释放”,那么ControllerA 就需要在ControllerB 不再需要对象M的时候,将 M 销毁。但ControllerB可能只是临时用一下对象 M,也可能觉得对象 M 很重要,将它设置成自己的一个成员变量,那这种情况下,什么时候销毁对象 M 就成了一个难题。

场景2:

对于这种情况,有一个暴力的做法,就是ControllerA 在调用完ControllerB之后,马上就销毁参数对象 M,然后ControllerB 需要将参数另外复制一份,生成另一个对象 M2,然后自己管理对象 M2 的生命期。但是这种做法有一个很大的问题,就是它带来了更多的内存申请、复制、释放的工作。本来一个可以复用的对象,因为不方便管理它的生命期,就简单的把它销毁,又重新构造一份一样的,实在太影响性能。

场景3:

我们另外还有一种办法,就是ControllerA 在构造完对象 M 之后,始终不销毁对象 M,由ControllerB 来完成对象 M 的销毁工作。如果ControllerB 需要长时间使用对象 M,它就不销毁它,如果只是临时用一下,则可以用完后马上销毁。这种做法看似很好地解决了对象复制的问题,但是它强烈依赖于 ControllerA、ControllerB 两个对象的配合,代码维护者需要明确地记住这种编程约定。而且,由于对象 M 的申请是在ControllerA中,释放在ControllerB 中,使得它的内存管理代码分散在不同对象中,管理起来也非常费劲。如果这个时候情况再复杂一些,例如ControllerB 需要再向ControllerC传递对象 M,那么这个对象在ControllerC 中又不能让对象 C 管理。所以这种方式带来的复杂性更大,更不可取。

所以引用计数很好的解决了这个问题,在参数 M 的传递过程中,哪些对象需要长时间使用这个对象,就把它的引用计数加 1,使用完了之后再把引用计数减 1。所有对象都遵守这个规则的话,对象的生命期管理就可以完全交给引用计数了。我们也可以很方便地享受到共享对象带来的好处。