iOS & OS X 内存管理

内存管理其实就是在需要的时候分配内存、使用、使用完后释放的过程。一个好的程序会尽可能的使用更少的内存资源。在Objective-C中对内存管理的原则是管理好对象的生命周期,在不再需要的时候释放,确保内存中没有多余的对象。

Objective-C中提供了两种内存管理方式:

  1. 手动内存管理(MRR),也就是你需要精确的管理好自己所拥有的对象,这是用过引用计数系统来实现的,而引用计数的实现则是依赖NSObject类和运行时系统;
  2. 自动引用计数(ARC),和MRR一样也是依赖引用计数,不同的是编译器会在编译时自动插入合适的内存管理的方法。建议在新的项目组使用ARC,这样不仅可以省掉很多麻烦,也可以提升程序运行效率。

通常我们所说的内存问题基本上分两种:

  • 释放或覆盖掉了还在使用的数据,通常会导致程序崩溃、数据损坏或其他问题;
  • 没有释放掉不再需要的数据导致内存泄露,内存泄露是分配的内存没法释放,导致程序占用内存越来越多,会影响系统运行效率,甚至导致程序终止;

内存管理规则

虽然Objective-C中内存管理依赖引用计数,但是你在实际使用中不应该将关注点放在对象的Retain Count上,而只需要关注对象的所有权。一个对象可以有一个或多个持有者,只要对象存在持有者它就会一直存在,一但没有持有者,对象就会被运行时系统自动销毁。所以你只需要弄清楚哪些对象是你持有的,哪些不是。

  • 自己创建的对象自己持有
    你能通过以allocnewcopymutableCopy开头的方法创建自己持有的对象。

  • 不是自己创建的对象,自己也能持有
    你可以调用retain方法来持有不是自己创建的对象。

  • 不再需要自己持有的对象时释放它
    通过调用releaseautorelease方法释放对象的所有权。

  • 不要释放非自己持有的对象

引用计数实现原理

NSObject类alloc的实现:

1
2
3
4
+alloc
+allocWithZone:
class_createInstance
calloc

alloc方法中先调用allocWithZone:类方法,该方法主要是分配对象内存空间并将该内存空间置空。然后调用class_createInstance函数计算对象占用内存大小并创建实例,最后根据计算出的对象大小由calloc来分配内存块。

retainCount:

1
2
3
-retainCount
__CFDoExternRefOperation
CFBasicHashGetCountOfKey

retain:

1
2
3
-retain
__CFDoExternRefOperation
CFBasicHashAddValue

release:

1
2
3
4
-release
__CFDoExternRefOperation
CFBasicHashRemoveValue
//CFBasicHashRemoveValue返回0时,-release调用dealloc)

其中retainCount, retain, release方法都调用到了__CFDoExternRefOperation函数,而__CFDoExternRefOperation根据三个操作分别调用CFBasicHashGetCountOfKeyCFBasicHashAddValueCFBasicHashRemoveValue来获取retainCount、给retainCount加1、给retainCount减1(当判断retainCount减到0时调用dealloc)。

autoreleasepool实现原理

autorelease自动释放,看起来像ARC,但更像C语言中的自动变量(超过了其作用域就自动废弃)。

autorelease使用方法:

  1. 生成NSAutoreleasePool对象;
  2. 调用已分配对象的autorelease方法;
  3. 释放NSAutoreleasePool对象
1
2
3
4
NSAutoreleasePool *pool = [[NSAutorelease alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain]; //等同于 [pool release];

在Cocoa框架中,会自动在程序的NSRunLoop或其他程序可运行的地方对NSAutoreleasePool对象进行管理(生成,持有、废弃)。所以开发者不一定非得显性的使用NSAutoreleasePool。

NSRunLoop每次循环开始时NSAutoreleasePool会自动生成,一个循环结束时NSAutoreleasePool对象被废弃。

只要所在的NSAutoreleasePool对象不被废弃,autorelease对象就不能被释放,所以有时候会出现内存不足的情况,如读取大量图片并进行相应处理的时候,将图片读到NSData对象,再生成UIImage对象,过程中会产生大量的autorelease对象,这时候有必要缩短NSAutorelasePool的周期,让每个循环就生成并废弃一个NSAutoreleasePool:

1
2
3
4
5
6
7
for (int i = 0; i < 图片数; i++) {
NSAutoreleasePool = [[NSAutoreleasePool alloc] init];
/*
* 产生autorelease对象
*/
[pool drain];
}

NSObject的autorelease方法的实现本质上是将当前的对象加入到最近一级的NSAutoreleasePool对象中。

另外Cocoa框架中也有很多方法返回autorelease对象,比如很多对象的构造器方法:

1
NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];

它等同于:

1
NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];

Cocoa中autorelease在runtime中通过C++类AutoreleasePoolPage实现了,该类中定义了三个主要方法:

  1. objc_autoreleasePoolPush(void);
  2. objc_autoreleasePoolPop(void *);
  3. objc_autorelease(id obj);
1
2
3
4
5
6
7
8
9
10
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 等同于调用 objc_autoreleasePoolPush()
id obj = [[NSObject alloc] init];
[obj autorelease];
// 等同于调用 objc_autorelease(obj)
[pool drain];
// 等同于 objc_autoreleasePoolPop(pool)

ARC的实现原理

ARC下内存管理规则发生了一些改变:

  • 自己生成的对象自己持有
  • 非自己生成的对象自己也能持有
  • 自己持有的对象不再需要手动释放
  • 不要释放非自己持有的对象

ARC模式下增加了四种修饰符:__strong__weak__unsafe_unretained__autoreleasing

__strong是ARC模式下对象的默认修饰符,即:

1
2
id obj = [[NSObject alloc] init]
//等同于:id __strong obj = [[NSObject alloc] init];

为了避免两个对象相互强引用对方导致内存泄露,就引入了__weak修饰符,与__strong不同,__weak申明的变量不持有对象(弱引用)。

在iOS5和OS X Lion以下的系统版本中__weak修饰符是不能使用的,替代的是__unsafe_unretained修饰符,它与__weak修饰符的主要区别是申明对象和对象释放时,不能保证对象的指针为空。

在实际编码中较少使用到__autoreleasing修饰符,因为ARC对象在超过作用域后会自动被回收。__autorelasing的作用类似非ARC模式的给对象调用autorelease方法,一般可以配合autoreleasePool使用:

1
2
3
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}

ARC模式下用@autoreleasepool块代替了NSAutoreleasePool对象的创建和销毁。

ARC实现

ARC的实现依赖编译器及Objective-C运行时库,并通过新增的修饰符完成。

__strong

__strong对象是在作用域结束时编译器自动插入release代码。

1
2
3
{
id __strong obj = [[NSObject alloc] init];
}

等价于代码

1
2
3
4
//模拟代码
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);

在不使用alloc/new/copy/mutableCopy构造对象的时候,如:

1
2
3
{
id __strong obj = [NSMutableArray array];
}

等价于:

1
2
3
4
//模拟代码
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleaseReturnValue(obj);
objc_release(obj);

其中objc_retainAutoreleaseReturnValue()函数用于持有(retain)注册到autoreleasepool中的对象。与之对应的函数:objc_autoreleaseReturnValue(),它在返回一个注册到autoreleasepool中的对象时使用,如:

1
2
3
4
5
+ (id)array {
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj);
}

__weak

  • 带__weak修饰符的变量引用的对象被废弃时会自动被赋值为nil
  • 使用__weak修饰符的变量是使用注册到autoreleasepool中的对象
1
2
3
{
id __weak obj1 = obj;
}

等价于:

1
2
3
4
//模拟代码
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);

objc_initWeak()函数会先将__weak修饰变量初始化为空,然后再调用objc_storeWeak()将参数对象赋值给变量:

1
2
obj1 = 0;
objc_storeWeak(&obj1, obj);

objc_destroyWeak()函数是调用objc_storeWeak()函数将空值赋给变量:

1
objc_storeWeak(&obj1, 0);

即上面整个过程等同于:

1
2
3
4
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);

__autoreleasing

前面说过使用__autoreleasing修饰符等同于非ARC模式下对象调用autorelease方法。

1
2
3
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}

等同于:

1
2
3
4
5
6
//模拟代码
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
obj = objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);