内存管理其实就是在需要的时候分配内存、使用、使用完后释放的过程。一个好的程序会尽可能的使用更少的内存资源。在Objective-C中对内存管理的原则是管理好对象的生命周期,在不再需要的时候释放,确保内存中没有多余的对象。
Objective-C中提供了两种内存管理方式:
- 手动内存管理(MRR),也就是你需要精确的管理好自己所拥有的对象,这是用过引用计数系统来实现的,而引用计数的实现则是依赖NSObject类和运行时系统;
- 自动引用计数(ARC),和MRR一样也是依赖引用计数,不同的是编译器会在编译时自动插入合适的内存管理的方法。建议在新的项目组使用ARC,这样不仅可以省掉很多麻烦,也可以提升程序运行效率。
通常我们所说的内存问题基本上分两种:
- 释放或覆盖掉了还在使用的数据,通常会导致程序崩溃、数据损坏或其他问题;
- 没有释放掉不再需要的数据导致内存泄露,内存泄露是分配的内存没法释放,导致程序占用内存越来越多,会影响系统运行效率,甚至导致程序终止;
内存管理规则
虽然Objective-C中内存管理依赖引用计数,但是你在实际使用中不应该将关注点放在对象的Retain Count上,而只需要关注对象的所有权。一个对象可以有一个或多个持有者,只要对象存在持有者它就会一直存在,一但没有持有者,对象就会被运行时系统自动销毁。所以你只需要弄清楚哪些对象是你持有的,哪些不是。
自己创建的对象自己持有
你能通过以alloc
、new
、copy
、mutableCopy
开头的方法创建自己持有的对象。不是自己创建的对象,自己也能持有
你可以调用retain
方法来持有不是自己创建的对象。不再需要自己持有的对象时释放它
通过调用release
或autorelease
方法释放对象的所有权。不要释放非自己持有的对象
引用计数实现原理
NSObject类alloc的实现:
|
|
alloc方法中先调用allocWithZone:
类方法,该方法主要是分配对象内存空间并将该内存空间置空。然后调用class_createInstance
函数计算对象占用内存大小并创建实例,最后根据计算出的对象大小由calloc
来分配内存块。
retainCount:
|
|
retain:
|
|
release:
|
|
其中retainCount, retain, release方法都调用到了__CFDoExternRefOperation
函数,而__CFDoExternRefOperation
根据三个操作分别调用CFBasicHashGetCountOfKey
、CFBasicHashAddValue
、CFBasicHashRemoveValue
来获取retainCount、给retainCount加1、给retainCount减1(当判断retainCount减到0时调用dealloc)。
autoreleasepool实现原理
autorelease自动释放,看起来像ARC,但更像C语言中的自动变量(超过了其作用域就自动废弃)。
autorelease使用方法:
- 生成NSAutoreleasePool对象;
- 调用已分配对象的autorelease方法;
- 释放NSAutoreleasePool对象
|
|
在Cocoa框架中,会自动在程序的NSRunLoop或其他程序可运行的地方对NSAutoreleasePool对象进行管理(生成,持有、废弃)。所以开发者不一定非得显性的使用NSAutoreleasePool。
NSRunLoop每次循环开始时NSAutoreleasePool会自动生成,一个循环结束时NSAutoreleasePool对象被废弃。
只要所在的NSAutoreleasePool对象不被废弃,autorelease对象就不能被释放,所以有时候会出现内存不足的情况,如读取大量图片并进行相应处理的时候,将图片读到NSData对象,再生成UIImage对象,过程中会产生大量的autorelease对象,这时候有必要缩短NSAutorelasePool的周期,让每个循环就生成并废弃一个NSAutoreleasePool:
|
|
NSObject的autorelease方法的实现本质上是将当前的对象加入到最近一级的NSAutoreleasePool对象中。
另外Cocoa框架中也有很多方法返回autorelease对象,比如很多对象的构造器方法:
|
|
它等同于:
|
|
Cocoa中autorelease在runtime中通过C++类AutoreleasePoolPage实现了,该类中定义了三个主要方法:
- objc_autoreleasePoolPush(void);
- objc_autoreleasePoolPop(void *);
- objc_autorelease(id obj);
|
|
ARC的实现原理
ARC下内存管理规则发生了一些改变:
- 自己生成的对象自己持有
- 非自己生成的对象自己也能持有
- 自己持有的对象不再需要手动释放
- 不要释放非自己持有的对象
ARC模式下增加了四种修饰符:__strong
、__weak
、__unsafe_unretained
、__autoreleasing
。
__strong
是ARC模式下对象的默认修饰符,即:
|
|
为了避免两个对象相互强引用对方导致内存泄露,就引入了__weak
修饰符,与__strong
不同,__weak
申明的变量不持有对象(弱引用)。
在iOS5和OS X Lion以下的系统版本中__weak
修饰符是不能使用的,替代的是__unsafe_unretained
修饰符,它与__weak
修饰符的主要区别是申明对象和对象释放时,不能保证对象的指针为空。
在实际编码中较少使用到__autoreleasing
修饰符,因为ARC对象在超过作用域后会自动被回收。__autorelasing
的作用类似非ARC模式的给对象调用autorelease
方法,一般可以配合autoreleasePool使用:
|
|
ARC模式下用@autoreleasepool块代替了NSAutoreleasePool对象的创建和销毁。
ARC实现
ARC的实现依赖编译器及Objective-C运行时库,并通过新增的修饰符完成。
__strong
__strong对象是在作用域结束时编译器自动插入release代码。
|
|
等价于代码
|
|
在不使用alloc/new/copy/mutableCopy构造对象的时候,如:
|
|
等价于:
|
|
其中objc_retainAutoreleaseReturnValue()函数用于持有(retain)注册到autoreleasepool中的对象。与之对应的函数:objc_autoreleaseReturnValue(),它在返回一个注册到autoreleasepool中的对象时使用,如:
|
|
__weak
- 带__weak修饰符的变量引用的对象被废弃时会自动被赋值为nil
- 使用__weak修饰符的变量是使用注册到autoreleasepool中的对象
|
|
等价于:
|
|
objc_initWeak()函数会先将__weak修饰变量初始化为空,然后再调用objc_storeWeak()将参数对象赋值给变量:
|
|
objc_destroyWeak()函数是调用objc_storeWeak()函数将空值赋给变量:
|
|
即上面整个过程等同于:
|
|
__autoreleasing
前面说过使用__autoreleasing修饰符等同于非ARC模式下对象调用autorelease方法。
|
|
等同于:
|
|