美文网首页
Autorelease学习笔记

Autorelease学习笔记

作者: AprSnow | 来源:发表于2018-03-21 17:41 被阅读7次

    前言

    本文收集了一些大神的博客,加上自己平时的一些理解,特此记录学习笔记。
    retainrelease 是 MFC 模式开发下,每个初级 iOSer 的心中痛,多少次 bug 都是因为内存管理不当引起的。
    Autorelease 有时是一个"神器",只要用[obj autorelease] ,很多 bug 迎刃而解。
    Autorelease 背后的机制是什么?是如何管理内存的?编译器又做了哪些操作?

    在苹果一些新的硬件设备上,autoreleasepool 和 runloop 的表现发生了变化,这一点需要详细查资料。

    Autorelease对象什么时候释放?

    表面上看答案是:“当前作用域大括号结束时释放”。

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

    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(哨兵对象) 作为入参,于是:

    1. 根据传入的哨兵对象地址找到哨兵对象所处的page
    2. 在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次 release 消息,并向回移动next指针到正确位置
    3. 从最新加入的对象一直向前清理,可以向前跨越若干个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

    1. 写非UI框架项目时
    2. 在一个循环中创建大量的临时对象
    3. 大量使用辅助线程

    所以,如下代码会产生很大的内存占用,甚至导致内存警告。

    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包围着
    }];
    

    参考

    1. 黑幕背后的Autorelease - sunnyxx
    2. Objective-C Autorelease Pool 的实现原理
    3. Using Autorelease Pool Blocks

    相关文章

      网友评论

          本文标题:Autorelease学习笔记

          本文链接:https://www.haomeiwen.com/subject/frtnqftx.html