学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

AutoreleasePool总结

为什么已经有了 ARC ,但还是需要 @autoreleasePool 的存在?
避免内存峰值,及时释放不需要的内存空间

面试题

一个很经典的面试题

1
2
3
4
5
6
 for (int i = 0; i < MAXFLOAT; i++) {
        NSString *string = @"stdy";
        string = [string lowercaseString];
        string = [string stringByAppendingString:@"123"];
        NSLog(@"--%@", string);
    }

上述的这种写法,会使内存慢慢增加,如何解决呢?自动释放池

1
2
3
4
5
6
7
8
for (int i = 0; i < MAXFLOAT; i++) {
        @autoreleasepool {
            NSString *string = @"stdy";
            string = [string lowercaseString];
            string = [string stringByAppendingString:@"123"];
            NSLog(@"--%@", string);
        }
    }

AutoreleasePool爆栈的问题

autoreleasePool 的自动 drain是靠runLoop进入空闲来触发的。那么如果你在执行一个很重的任务(如一个很多次数的循环)导致runLoop一直不能进入空闲。 那么autoreleasePool 里面对象会越来越多。导致内存爆掉。因此建议如果是循环次数很大的循环,要么循环里面不要产生autorelease的对象,要么就要手动 添加 @autorelease {}来避免内存爆掉。

1
2
3
4
//使用容器的block版本的枚举容器时,内部会自动添加一个AutoreleasePool
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];

什么时间会创建自动释放池?


从程序启动到加载完成是一个完整的运行循环,然后会停下来,等待用户交互,用户的每一次交互都会启动一次运行循环,来处理用户所有的点击事件、触摸事件,运行循环检测到事件并启动后,就会创建自动释放池。
子线程的 runloop 默认是不工作,无法主动创建,必须手动创建。
自定义的 NSOperation 和 NSThread 需要手动创建自动释放池。比如:自定义的 NSOperation 类中的 main 方法里就必须添加自动释放池。否则出了作用域后,自动释放对象会因为没有自动释放池去处理它,而造成内存泄露。

但对于 blockOperation 和 invocationOperation 这种默认的Operation ,系统已经帮我们封装好了,不需要手动创建自动释放池。

@autoreleasepool 当自动释放池被销毁或者耗尽时,会向自动释放池中的所有对象发送 release 消息,释放自动释放池中的所有对象。

如果在一个vc的viewDidLoad中创建一个 Autorelease对象,那么该对象会在 viewDidAppear 方法执行前就被销毁了。

访问 __weak 修饰的变量,是否已经被注册在了 @autoreleasePool 中?为什么?

肯定的,__weak修饰的变量属于弱引用,如果没有被注册到 @autoreleasePool 中,创建之后也就会随之销毁,为了延长它的生命周期,必须注册到 @autoreleasePool 中,以延缓释放。

函数返回一个对象时,会对对象 autorelease 么?为什么?

会 ,为了延长返回对象的生命周期,给其他使用者留足调用的时间

简要说一下 @autoreleasePool 的数据结构?

一个双向链表组成的栈

  1. AutoreleasePoolPage 组成了一个双向链表,每个page里面都有一个一页的大小的数组,如iOS上一页 0x4000。 每个AutoreleasePoolPage都有个next指针指向下一个可以存储的位置。当next是结尾的时候,这个page就full了。再压入对象就要创建新的page了。
  2. 一般情况下链表最尾端的一个page是 hotPage. 一般autorelease时对象指针会压入这个page。
  3. objc_autoreleasePoolPush 会返回当前page的 next的地址作为 token. 然后objc_autoreleasePoolPop 会一直倾倒到这个token的地址。
  4. push和pop一般出现的地方就是: ① @autorelease {} 所包围的代码的前后; ② runLoop 进入和退出时;③ runLoop进入kCFRunLoopBeforeWaiting状态时,也就是常常说的idle时,会先pop, 再 push.

@autoreleasrPool 的释放时机?

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

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

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

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

Autorelease对象是在当前runloop迭代结束时释放

Autorelease原理

AutoreleasePoolPage

ARC下使用@autoreleasepool{}来使用一个AutoreleasePool,随后编译器会将其改写成下面的样子

1
2
3
void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);

AutoreleasePoolPage结构—-C++实现的类

1
2
3
4
5
6
7
magic_t const magic;
id* next; //作为游标指向栈顶最新add进来的autorelease对象的地址
pthread const thread;
AutoreleasePoolPage* const parent;
AutoreleasePoolPage* child;
uint32_t const depth;
unit32_t hiwat;
  • AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小)
  • 一个AutoreleasePoolPage空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入

objc_autoreleasePoolPush

  • 当前线程只有一个AutoreleasePoolPage对象时的内存结构 上图中,这一页再加入一个autorelease对象就要满了,也就是next指针马上指向栈顶,这时就要建立下一页page对象,与这一页链表连接完成后,新page的next指针被初始化在栈底(begin的位置),然后继续向栈里添加新对象。

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

objc_autoreleasePoolPop

每当进行一次objc_autoreleasePoolPush调用时,runtime会向当前的AutoreleasePoolPage中add进一个哨兵对象,值为0(也就是nil),那么page就变成下面的样子

objc_autoreleasePoolPush的返回值就是这个哨兵对象的地址,objc_autoreleasePoolPop(哨兵对象)作为入参

  • 释放过程
  1. 根据传入的哨兵对象地址找到哨兵对象所处的page
  2. 在当前page中,将晚于哨兵对象(可根据parent跨越page)插入的所有autorelease对象都发送一次release消息,并向回移动next指针到正确位置

上面Pop之后的内存状态如下

嵌套的AutoreleasePool

pop的时候总会释放到上次push的位置为止,多层的pool就是多个哨兵对象而已

文章参考

1.黑幕背后的Autorelease