前言
本文收集了一些大神的博客,加上自己平时的一些理解,特此记录学习笔记。
retain
和 release
是 MFC 模式开发下,每个初级 iOSer 的心中痛,多少次 bug 都是因为内存管理不当引起的。
Autorelease
有时是一个"神器",只要用[obj autorelease]
,很多 bug 迎刃而解。
Autorelease
背后的机制是什么?是如何管理内存的?编译器又做了哪些操作?
在苹果一些新的硬件设备上,autoreleasepool 和 runloop 的表现发生了变化,这一点需要详细查资料。
Autorelease对象什么时候释放?
表面上看答案是:“当前作用域大括号结束时释放”。
本质上是:在没有手加 Autorelease Pool
的情况下,Autorelease
对象是在当前的 runloop
迭代结束时释放的,而它能够释放的原因是系统在每个 runloop
迭代中都加入了自动释放池 Push 和 Pop。
RunLoop:
Each NSThread object, including the application’s main thread, has an NSRunLoop object automatically created for it as needed.
在主线程的 NSRunLoop
对象(在系统级别的其他线程中应该也是如此,比如通过 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
获取到的线程)的每个 event loop
开始前,系统会自动创建一个 autoreleasepool
,并在 event loop
结束时 drain 。
autoreleasepool
是与线程紧密相关的,每一个 autoreleasepool
只对应一个线程。
autoreleasepool
编译器会将 @autoreleasepool{}
改写成:
void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);
这两个函数都是对 AutoreleasePoolPage
的简单封装:
class AutoreleasePoolPage {
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
};
-
AutoreleasePool
并没有单独的结构,而是由若干个AutoreleasePoolPage
以 双向链表 的形式组合而成(分别对应结构中的parent指针和child指针) -
AutoreleasePool
是按线程一一对应的(结构中的thread指针指向当前线程) -
AutoreleasePoolPage
每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease
对象的地址 - 上面的
id *next
指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置 - 一个
AutoreleasePoolPage
的空间被占满时,会新建一个AutoreleasePoolPage
对象,连接链表,后来的autorelease对象在新的page加入
向一个对象发送 autorelease
消息,就是将这个对象加入到当前 AutoreleasePoolPage
的栈顶next指针指向的位置
释放
每当进行一次 objc_autoreleasePoolPush
调用时,runtime向当前的 AutoreleasePoolPage
中add进一个哨兵对象,值为0(也就是个nil)
objc_autoreleasePoolPush
的返回值正是这个哨兵对象的地址,被 objc_autoreleasePoolPop(哨兵对象)
作为入参,于是:
- 根据传入的哨兵对象地址找到哨兵对象所处的page
- 在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次
release
消息,并向回移动next指针到正确位置 - 从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page
使用
Apple
官方文档 "Using Autorelease Pool Blocks" 中提到了三个场景需要使用 @autoreleasepool
。
three occasions when you might use your own autorelease pool blocks:
If you are writing a program that is not based on a UI framework, such as a command-line tool.
If you write a loop that creates many temporary objects.
If you spawn a secondary thread
- 写非UI框架项目时
- 在一个循环中创建大量的临时对象
- 大量使用辅助线程
所以,如下代码会产生很大的内存占用,甚至导致内存警告。
for (int i=0; i<100000; i++) {
UIImage *image = [UIImage imageNamed:@"pic"];
}
在for循环中大量创建临时变量时,需要用@autoreleasepool来优化:
for (int i=0;i<100000;i++) {
@autoreleasepool {
UIImage *image = [UIImage imageNamed:@"pic"];
}
}
特别
使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
网友评论