概述

Autorelease机制是为了延时开释目标,即在超出开释池生命周期后,向其办理的目标实例发送release音讯

原理

在每个主动开释池创立的时分,会在当时的AutoreleasePoolPage中设置一个符号位,在此期间,当有目标调用autorelease时,会把目标增加AutoreleasePoolPage

若当时页增加满了,会初始化一个AutoreleasePoolPage,然后用双向链表连接起来,并把新初始化的这一页设置为hotPage,当主动开释池pop时,从最下面顺次往上pop,调用每个目标的release办法,直到遇到标志位

MRC下运用主动开释池

MRC环境下,经过NSAutoreleasePool目标来办理目标,对于一切调用过autorelease办法的目标,在抛弃NSAutoreleasePool目标时,都将调用release实例办法

// 第一步:生成并持有开释池NSAutoreleasePool目标
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 第二步:调用目标的autorelease实例办法
id obj = [[NSObject alloc] init];
[obj autorelease];
// 第三步:抛弃NSAutoreleasePool目标,即向pool办理的一切目标发送音讯,相当于[obj release]
[pool drain];

ARC下运用主动开释池

ARC环境下不能运用NSAutoreleasePool类,也不能调用autorelease办法,而是经过@autoreleasepool块和__autoreleasing修饰符

如下图所示,

1、@autoreleasepool替代NSAutoreleasePool类目标的生成、持有以及抛弃这一进程

2、经过__autoreleasing修饰符的变量,替代autorelease办法,将目标注册到了AutoreleasePool

自动释放池 ~ AutoreleasePool

// ARC优化:可省掉__autoreleasing
@autoreleasepool {
    id obj = [[NSObject alloc] init];
    NSLog(@"%s %@", __func__, obj);
 }

底层原理

经过clang编译器将以上代码编译成C++源码

{
    __AtAutoreleasePool __autoreleasepool; // 对应以上代码中的@autoreleasepool
    id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_15_7m893_bx0_x7vt0_x2yk610w0000gp_T_main_a49e01_mi_0, __func__, obj);
} // 大括号对应开释池的效果域

结构体__AtAutoreleasePool的详细完成

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);
struct __AtAutoreleasePool {
    // 构造函数,回来鸿沟目标atautoreleasepoolobj
    // 在程序履行到声明这个目标的位置时调用
    __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
    // 析构函数,传入鸿沟目标atautoreleasepoolobj
    // 在程序履行到离开这个目标的效果域时调用
    ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};
// 简化以上代码如下
int main {
    void *atautoreleasepoolobj = objc_autoreleasePoolPush();
    ...
    objc_autoreleasePoolPop(atautoreleasepoolobj);    
}

objc_autoreleasePoolPush与objc_autoreleasePoolPop

void *objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
    AutoreleasePoolPage::pop(ctxt);
}

AutoreleasePoolPage

每个AutoreleasePoolPage目标占用4M内存,用于寄存本身内部的成员变量,剩下的空间用来寄存autorelease目标的地址

每个主动开释池都是由若干个AutoreleasePoolPage组成的双向链表结构,如下图

自动释放池 ~ AutoreleasePool

哨兵目标即鸿沟目标(POOL_BOUNDARY)

#define POOL_BOUNDARY nil
  • 每逢主动开释池初始化调用objc_autoreleasePoolPush办法时,总会经过AutoreleasePoolPagepush办法,将POOL_BOUNDARY放到当时page的栈顶,而且回来这个鸿沟目标
  • 每逢主动开释池开释调用objc_autoreleasePoolPop办法时,又会将鸿沟目标以参数传入,这样主动开释池就会向开释池中目标发送release办法,直至找到第一个鸿沟目标为止

Runloop与AutoreleasePool的关系

主线程的NSRunloop在监测到事情呼应敞开每一次事情循环之前,会主动创立一个autoreleasepool,而且会在事情循环结束的时分履行drain操作,开释其中的目标

AutoreleasePool在主线程上的开释机遇

  • App 发动后,苹果在主线程 RunLoop 里注册了两个Observer,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()
  • 第一个Observer监督的事情是Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush()创立主动开释池。其order是 -2147483647,表明其优先级最高,保证创立开释池发生在其他一切回调之前
  • 第二个Observer监督了两个事情:

1、BeforeWaiting(预备进入休眠) 时调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush() 开释旧的池并创立新池

2、Exit(即将退出Loop)时调用 _objc_autoreleasePoolPop()来开释主动开释池,其 order是 2147483647,优先级最低,保证其开释池子发生在其他一切回调之后

  • 在主线程履行的代码,通常是写在诸如事情回调、Timer回调内的。这些回调会被 RunLoop 创立好的AutoreleasePool环绕着,所以不会呈现内存走漏,开发者也不必显示创立 Pool

自动释放池 ~ AutoreleasePool

AutoreleasePool子线程上的开释机遇

每一个线程都会保护自己的AutoreleasePool栈,所以子线程虽然默许没有敞开Runloop,可是依然存在AutoreleasePool,在子线程退出的时分会去开释autorelease目标

从源码剖析的视点来看,如果子线程中没有创立AutoReleasePool,而一旦发生了 autorelease目标,就会调用autoreleaseNoPage办法主动创立hotPage,并将目标加入到其栈中。所以,一般情况下,子线程即时我们不手动增加主动开释池,也不会发生内存走漏

AutoreleasePool需求手动增加的情况

  1. 编写的不是基于UI结构的程序,例如命令行东西

  2. 经过循环方式创立很多暂时目标

  3. 运用非Cocoa程序创立的子线程

    // 创立很多暂时目标 // 以手动干涉的方式及时开释不需求的目标,削减内存耗费 for (int i = 0; i < 10000000; i++) { @autoreleasepool { NSObject *obj = [[NSObject alloc] init]; NSLog(@”打印obj %@”, obj); } }