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