autorelease

1、自动释放池的实现原理是什么?

1、App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。

2、第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

3、第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

4、在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

核心方法就是 :

1
2
3
4
5
6
7
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}

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

而 AutoreleasePollPage 的定义是这样的:

1
2
3
4
5
6
7
8
9
class AutoreleasePoolPage {
magic_t const magic; //用于对当前 AutoreleasePoolPage 完整性的校验
id *next;//下一个为空的内存地址
pthread_t const thread; //保存了当前页所在的线程
AutoreleasePoolPage * const parent; //用来构造双向链表
AutoreleasePoolPage *child;//用来构造双向链表
uint32_t const depth;
uint32_t hiwat;
};

每一个自动释放池都是由一系列的 AutoreleasePoolPage 组成的,并且每一个 AutoreleasePoolPage 的大小都是 4096 字节(16 进制 0x1000)

在内存中会以栈的形式,先把成员变量存到栈底,然后剩下的空间用来存储加入到自动释放池中的对象。

begin() 和 end() 方法分别指向可存放 自动释放对象 的边界指针
next 指向了下一个为空的内存地址,如果 next 指向的地址加入一个 object,它就会移动到下一个为空的内存地址中。

POOL_SENTINEL(哨兵对象) 等价于nil

POOL_SENTINEL nil ```
1
2
3
4
5
6
7
8

在每个自动释放池初始化调用 objc_autoreleasePoolPush 的时候,都会把一个 POOL_SENTINEL push 到自动释放池的栈顶,并且返回这个 POOL_SENTINEL 哨兵对象。

而当方法 objc_autoreleasePoolPop 调用时,就会向自动释放池中的对象发送 release 消息,直到第一个 POOL_SENTINEL。

单个自动释放池的执行过程就是 `objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void *)。`

调用栈:

  • [NSObject autorelease]
    └── id objc_object::rootAutorelease()
    └── id objc_object::rootAutorelease2()
    └── static id AutoreleasePoolPage::autorelease(id obj)
        └── static id AutoreleasePoolPage::autoreleaseFast(id obj)
            ├── id *add(id obj)
            ├── static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
            │   ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
            │   └── id *add(id obj)
            └── static id *autoreleaseNoPage(id obj)
                ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                └── id *add(id obj)
    
    1
    2
    3
    4
    5
    6
    7

    向一个对象发送- autorelease消息,就是将这个对象加入到当前AutoreleasePoolPage的栈顶next指针指向的位置




    # 2、main函数里有个 @autoreleasepool 是怎么回事?

int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

1
2

实际上代码是:

{
AtAutoreleasePool autoreleasepool;
}

1
而__AtAutoreleasePool 是:

struct AtAutoreleasePool { AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};

1
2

所以本质上main函数可以看成:

int main(int argc, const char argv[]) {
{
void
atautoreleasepoolobj = objc_autoreleasePoolPush();

    // do whatever you want

    objc_autoreleasePoolPop(atautoreleasepoolobj);
}
return 0;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@autoreleasepool 只是帮助我们少写了这两行代码而已,让代码看起来更美观,然后要根据上述两个方法来分析自动释放池的实现。





# 3、autoreleasepool 是什么时间释放的?

1、在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop

在即将进入runloop中的时候,调用 _objc_autoreleasePoolPush() 创建自动释放池,优先级最高,在其他操作之前完成释放池创建

BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;

Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池,这个优先级最低,等所有操作处理完再释放对象。

2、手动添加的自动释放池,会在作用域结束立即释放

  • (void)viewDidLoad {
    [super viewDidLoad];
    @autoreleasepool {
    NSString *str = [NSString stringWithFormat:@"xx"];
    
    }
    NSLog(@”%@”, str); // Console: (null)
    }
    1
    2
    3
    4
    5


    # 4、常见知识点

    1\使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
`

2、for循环中遍历产生大量autorelease变量时,就需要手加局部AutoreleasePool咯。

5、参考资料:

自动释放池的前世今生 —- 深入解析 autoreleasepool
AutoreleasePool底层实现原理
黑幕背后的Autorelease