OC中的内存管理其一:MRC

前言

本文的内容由我个人通过阅读《Objective-C高级编程》 总结而来,尽管第一章作者命名为自动引用计数,但经过我个人理解,第一章的内容已经显然超过单纯介绍自动引用计数,而是比较全面的介绍了OC中内存管理的前世今生,因此我认为“内存管理”更适合作为本章的标题,文中的内容并非完全来自书中,其中也加入了我通过查阅获取的知识,以及我个人的一些见解。

C语言中的内存管理

在了解OC的内存管理之前,我们首先要知道OC是C语言的超集,因此了解C语言的内存管理是必不可少的前置基础。

操作系统在加载了C代码后会将内存分为四种区域(这四种区域并非完全是连续的,我注意到有些博客将内存模型以图示的方式将一个内存分为四块,但这容易使人们误以为它们是四块连续的区域。)接着我们来一一了解这四种区域,但由于深入理解可能会偏离主题,因此每种区域只做必要的介绍。

栈(stack)

  • 这是一块连续内存区域,它的大小在编译期就已经确定,我们常说它是由编译器决定的(当然最终还是人类决定的),空间也非常有限。
  • 这一块区域存放的是局部变量。
  • 内存空间的申请和释放也是由编译器自动完成,我们只需要声明和赋值,内存便会自动申请,当执行到局部变量作用域的末尾,内存也会自动释放。

堆(heap)

  • 堆是内存中不连续的部分,它的空间很大,受限于系统和应用程序,以及计算机的虚拟内存大小,当然最终还是受限于编译器,理论上而言32位的应用程序中堆的空间最大为4G(但是不会达到这个数字,编译器会对它进行限制,因为其他的区域会占用一部分内存,操作系统也会占用一部分内存)。
  • 这一块区域存放的是我们在代码中动态申请(malloc、calloc、realloc) 的内容
  • 当申请的内容不再需要时,我们需要在代码中主动释放它们(free),如果不释放,那么这一块内存将会一直被占用直到程序结束。(内存泄漏)

关于后面两个区域的介绍在网络上非常至少,绝大多数地方都是一笔带过,希望大家能补充,当然这两个区域我们也不必过多讨论

全局/静态区(static)

  • 这个区域空间很大(具体多大不清楚,如何划分不清楚,是否连续不清楚,是否与堆共享不清楚)
  • 这个区域存放的是全局变量和静态变量。
  • 程序结束时释放。

常量区

  • 同样的,这个区域的空间依旧很大,但是细节都不清楚。
  • 这个区域存放各种常量。
  • 程序结束时释放。

(说实话我个人对常量区是否存在心存怀疑。)

OC中的内存管理

我们使用这样的方式在OC中初始化一个对象实例:

MyObject *m = [[MyObject alloc]init];

赋值符号右侧的两个消息分别是:

  • alloc,申请一块内存空间,返回一个未初始化的对象实例指针,并使接受它的指针变量持有这个对象。
  • init,初始化方法(构造函数),将对象初始化,返回一个初始化后的实例指针。

赋值符号左侧的m则是一个MyObject类型的指针。

不难理解,左侧的m属于变量,它存放在栈区或者全局区中(大部分情况在栈中)。

而右侧申请的内存空间实际上是在C语言中用malloc申请的,那么它占用的是堆中的空间。

我们知道堆中的内存占用需要我们主动去释放,那么该如何释放便是本文的重点。

手动引用计数(Manual Reference Counting)

早期的OC采用MRC的方式进行内存管理。

每个OC对象都会有一个引用计数器。

内存申请的命名规则

  • alloc
  • new
  • copy
  • mutableCopy

以如上四个单词开头 ,并且是驼峰拼写的方法,通常会认为是某个对象的内存申请方法,因此我们必须返回这个对象,尽管我们不这么做编译器也不会报错,但要养成良好的习惯。

持有对象(retain)

我们在实例化一个对象时,必定会用一个指针指向它,而当我们以

MyObject *m = [[MyObject alloc]init];

这种方式获取到一个对象实例时,该对象知道有一个指针指向并持有了它,它的引用计数器便会置为1。

那么不难想到,当后续还有其他指针持有它时,它的引用计数器便会不断增加。

从我的《OC中的@property及属性修饰符》一文中可以知道,MRC下变量的默认属性为assign,而这种属性的指针变量只是指向这个对象的话,并不代表持有了它,例如

MyObject *n = m;

那么如何让n也持有这个对象呢?

很简单,我们只需要接着这么做。

[n retain];

如此一来,这个对象的引用计数器便会从1增长到2。

释放对象(release)

而当n不再需要指向它时,我们必须释放它的持有:

[n release];

此时这个对象的引用计数从2减少为1,注意没有持有对象的指针不能使用release。

而当m也进行relaese后,对象的引用将会减至0,此时,系统会自动调用对象的dealloc(析构函数,其中包含free函数以释放内存)。

自动释放(autorelease)

每次都需要手动释放对象未免显得有些麻烦,因此MRC下还有一种自动释放对象的方法。

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];

MyObject *m = [[MyObject alloc]init];

[m autorelease];

[pool drain];

观察如上代码,创建一个NSAutoreleasePool对象,在它的生存周期内,我们只需要在m持有对象后使用autorelease,便可不必再担心它的释放,对象也会被pool持有(此时retaincount+1),它会在[pool drain]后自动释放,不难猜测,[pool drain]实际上也释放了pool。 [m autorelease]这一过程,我们也称为将这个指针所指的对象注册到AutoreleasePool。

自动引用计数(Automatic Reference Counting)

自iOS 5/ Mac OS X 10.7/Xcode4.2后,OC便添加了ARC的内存管理方式。

理解了MRC,便不难理解ARC。

  • 在ARC环境下,strong替代assign成为了OC指针变量的默认属性,这也意味着我们不再需要使用retain消息来持有对象,直接指向对象,便可以持有它。

  • 同样的,我们也不再需要使用release来释放对象,当指针超过其作用域时,指针会自动释放。

看上去ARC可以非常完美的管理对象的引用与释放,然而在实际使用中仍会出现很多问题,考虑接着写可能会使篇幅过长,下面的内容放到下一篇再总结。

本篇到此为止,希望这对你有帮助,如果有错误或是有需要补充的地方,望告知。


种一棵树最好的时间是在十年前,而后是现在。

Loading Disqus comments...
Table of Contents