前言:因为所以,闲的蛋疼,搞搞事,撸一下NSAutoreleasePool。
autoreleasePool是什么?什么是autoreleasePool?
先来一段代码:
//MRC
id object;
{
NSString *str1 = @"1";
NSString *str2 = @"2";
NSArray *array = @[str1, str2];
NSLog(@"array: %ld", CFGetRetainCount(array));
object = [NSArray arrayWithArray: array];
NSLog(@"array2: %ld", CFGetRetainCount(array));
NSLog(@"object: %ld", CFGetRetainCount(object));
[array release];
}
2018-01-18 20:29:11.307512+0800 BasicDemo[50165:1729663] array: 1
2018-01-18 20:29:11.307789+0800 BasicDemo[50165:1729663] array2: 1
2018-01-18 20:29:11.307831+0800 BasicDemo[50165:1729663] object: 1
想想在 MRC 时代,需要手动插入 retain 和 release,比如上面的代码在一个函数域中,array 是局部变量,object 是全局变量,一旦函数调用完毕,需要手动调用 release,释放掉 array, 但是,这里的全局变量并不持有 array,也就是说,一旦 array 被释放掉了,全局变量 object 也会变成 nil,这时候访问 object 会有 crash 的风险。那怎么办呢?
有两个方法:
方法一:对象object想长期持有array,可以直接retain数组array。
方法二:假如object只是想暂时持有array,这时候可以使用autoreleasePool来延时释放对象array。
所以,autoreleasePool 主要作用就是延时释放对象,释放对象也是调用release方法。在 ARC 时代,一般很少用autoreleasePool,因为,我们创建对象一般会用strong或者copy来修饰对象,这些修饰词会让对象持有所赋值对象或者新开辟一块内存,创建一个新的对象持有,所以,不必担心。
ARC与autoreleasePool
ARC和autoreleasePool其实是不同的机制,两者并没有冲突。无论MRC还是ARC都有autoreleasePool,autoreleasePool只是延时释放对象的一种机制。可以实现在不持有对象的所有权的情况下,使用对象,不持有也就不用担心对象的内存管理问题,只管使用就好。
ARC环境下,什么对象会加入autoreleasePool?
(1)alloc/new/copy/mutableCopy等持有对象的方法,不会加入autoreleasePool;其他不持有对象的方法通过objc_autoreleaseReturnValue和objc_retainAutoreleasedReturnValue来判断是否需要加入autoreleasePool,这是编译器的优化。
id object = [[NSObject alloc] init]; //自己生成并持有对象,不需要加入autoreleasePool
id object2 = [NSMutableArray array]; //MRC:不是自己生成并且不持有对象,需要加入autoreleasePool
id object3 = [NSMutableArray array];
[object3 retain]; //MRC:不是自己生成,但持有对象,不需要加入autoreleasePool
(2)iOS5及之前的编译器,关键字__weak修饰的对象,会自动加入autoreleasePool;iOS5及之后的编译器,则直接调用的release,不会加入autoreleasePool。
//ARC源码
id obj = [[NSObject alloc] init];
{
id __weak obj1 = obj;
NSLog(@"%@", obj1);
}
/* iOS5及之前编译器做法 */
id obj1;
objc_initWeak(&obj1, obj);
-----------------------
//调用方法之前,先新生成一个临时对象retain,然后再将该临时对象加入autoreleasePool,这样可以避免对象提前释放掉
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
-----------------------
//对象释放掉,清除所有的弱引用,并将对象置为nil
objc_destroyWeak(&obj1);
/* 现在的编译器做法*/
id obj = objc_msgSend(NSObject, "new");
id obj1;
objc_initWeak(&obj1, obj);
//调用方法前,先新生成一个临时对象retain
-----------------------
id tmp = objc_loadWeakRetained(obj1);
NSLog(@"%@", obj1);
//方法调用完之后,再release该临时对象,没有加入autoreleasepool
objc_release(tmp);
-----------------------
//释放对象
objc_destroyWeak(&obj1);
(3)id指针和对象指针(id *,NSError **),会自动加上关键字__autorealeasing,加入autoreleasePool。
资料参考:
《黑幕背后的Autorelease》,《iOS高级编程--内存管理》,Why __weak object will be added to autorelease pool?,《iOS高级编程--内存管理》。
线程 & runLoop & autoreleasePool
如下图:
由 ibireme 大神的 深入理解RunLoop可以知道:
(1)在cocoa框架下,每个线程都会有一个默认不开启的runLoop(主线程的runLoop默认是开启)。
(2)每个runLoop会创建autoreleasePool。
(3)runLoop即将进入休眠或者runLoop即将退出时,会释放旧的autoreleasePool对象,创建新的autoreleasePool对象。
苹果官方文档 Advanced Memory Management Programming Guide还可以知道:
(1)cocoa应用里每个线程都维护自己的一个autoreleasePool栈。
(2)如果循环产生很多临时变量,可以使用本地的autoreleasePool来降低内存的峰值。
(3)子线程,如果没有开启RunLoop,是不会自动创建autoreleasePool;但是,如果出现autorelease对象,则会自动创建autoreleasePool。
参考: does NSThread create autoreleasepool automatically now?以及 Objective-C Autorelease Pool 的实现原理。
autoreleasePool实现原理
可通过objc库的NSObject.mm来查看autorelease的实现(推荐一个编译Runtime源码的blog)。
这里会说一些理解的关键点,更详细的分析请移步:黑幕背后的Autorelease,Objective-C Autorelease Pool 的实现原理。
首先,使用clang命令,获得@autoreleasepool的cpp文件:
/* @autoreleasepool */
{
__AtAutoreleasePool __autoreleasepool;
}
__AtAutoreleasePool是一个结构体:
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
//构造函数调用的是objc_autoreleasePoolPush()
//析构函数调用的是objc_autoreleasePoolPop(atautoreleasepoolobj)
可以得出autoreleasepool的创建和释放代码如下:
void *atautoreleasepoolobj = objc_autoreleasePoolPush();
...
objc_autoreleasePoolPop(atautoreleasepoolobj)
因为这两个函数都是extern修饰的,想要查看具体的实现还需要去看objc的源码
在NSObject.mm源码可以找到AutoreleasePoolPage类:
class AutoreleasePoolPage
{
...
#define POOL_BOUNDARY nil
static size_t const SIZE = 4096; //AutoreleasePoolPage的内存大小
static size_t const COUNT = SIZE / sizeof(id);
magic_t const magic; //一些校验信息
id *next; //当前加入autoreleasepool对象的下一个位置
pthread_t const thread; //当前使用autoreleasePool的线程
AutoreleasePoolPage * const parent; //上一个autoreleasePage
AutoreleasePoolPage *child; //下一个autoreleasePage
uint32_t const depth; //autoreleasePage的深度
uint32_t hiwat;
static inline void *push() {
//创建并持有NSAutoreleasePool对象
}
static inline void pop(void *token) {
//废弃NSAutoreleasePool对象,释放pool里面的所有对象
}
static inline id autorelease(id obj) {
...
//将obj对象加入NSAutoreleasePool里面
id *dest __unused = autoreleaseFast(obj);
...
return obj;
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) { //page存在并且没有满,则添加当前对象
return page->add(obj);
} else if (page) { //page存在,但是page已满,则调用autoreleaseFullPage(obj, page)
return autoreleaseFullPage(obj, page);
} else { //page不存在,则调用autoreleaseNoPage(obj)
return autoreleaseNoPage(obj);
}
}
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
...
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full()); //寻找到不为空的page,否则,新建一个空的page
setHotPage(page);
return page->add(obj);
}
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
...
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
}
id *add(id obj) {
//添加对象
}
id * begin() {
//当前autoreleasePage开始存放对象的地址
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
//当前autoreleasePage的最高地址
return (id *) ((uint8_t *)this+SIZE);
}
bool empty() {
//当前的autoreleasePage已满
return next == begin();
}
bool full() {
//当前autoreleasePage为空
return next == end();
}
void releaseAll()
{
//释放当前缓冲池的所有对象
}
}
autoreleasePool的组成结构:
注意几点:
1.autoreleasePool调用push返回来的是hotPage里面的POOL_BOUNDARY对象的位置。
2.每个线程独立管理自己的自动释放池。
3.autoreleasePool由NSAutoreleasePoolPage组成,以双向链表的形式。
4.autoreleasePool可以嵌套,一个autoreleasePage可以包含一个或者多个autoreleasePool,一个autoreleasePool可以跨越多个autoreleasePage。
5.autoreleasePool释放对象是从next-1的位置开始释放,直到POOL_BOUNDARY的位置。
6.autoreleasePool如果有多个嵌套,释放的时候要从最里面的autoreleasePool开始释放,直到最外面的autoreleasePool。
7.调用autorelease方法,如果没有NSAutoreleasePoolPage,则会创建一个。
参考
黑幕背后的Autorelease
Objective-C Autorelease Pool 的实现原理
iOS中autorelease的那些事儿
@autoreleasepool-内存的分配与释放
深入理解RunLoop
Advanced Memory Management Programming Guide
Objective-C高级编程 iOS与OS X多线程和内存管理
引用计数带来的一次讨论
各个线程 Autorelease 对象的内存管理
iOS高级编程--内存管理