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
7void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
而 AutoreleasePollPage 的定义是这样的:
1 | class AutoreleasePoolPage { |
每一个自动释放池都是由一系列的 AutoreleasePoolPage 组成的,并且每一个 AutoreleasePoolPage 的大小都是 4096 字节(16 进制 0x1000)
在内存中会以栈的形式,先把成员变量存到栈底,然后剩下的空间用来存储加入到自动释放池中的对象。
begin() 和 end() 方法分别指向可存放 自动释放对象 的边界指针
next 指向了下一个为空的内存地址,如果 next 指向的地址加入一个 object,它就会移动到下一个为空的内存地址中。
POOL_SENTINEL(哨兵对象) 等价于nil1
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