美文网首页
Autorelease

Autorelease

作者: 随风流逝 | 来源:发表于2019-12-25 14:04 被阅读0次

    引用计数

    Objective-C内存管理中,每个对象都有属于自己的计数器;如果想让某个对象继续存活,就增加它的引用计数;当用完它之后,就减少该计数;当没人引用该对象,它的计数变为0之后,系统就把它销毁。

    所以,在objective-C的内存管理中,关键就在于对象释放的时机,autorelease的妙处在于,它找到了一个合适的时机来释放返回对象,这个时机就是本次消息循环结束的时候。我们只需要在返回对象前,调用autorelease,对象被加入autorelease pool(但没有减少对象的引用计数,所以这时候返回的对象仍是有效的),然后返回,程序继续执行,直到完成本次消息循环之时,再把autorelease pool中记录的临时对象一个个分别release--减少引用计数。

    AutoreleasePool

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

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

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

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

    Autorelease原理

    @autoreleasepool{ }

    使用clang编译器把main.m转化成main.cpp文件,查看一下@autoreleasepool{ }C++源码,可以看到@autoreleasepool{ }其实很简单,是一个__AtAutoreleasePool结构体,这个结构体的构造也很简单,一个构造函数和一个析构函数,构造函数中通过调用objc_autoreleasePoolPush()函数来实现构造;析构函数通过调用objc_autoreleasePoolPop()函数来实现析构。
    OC代码:

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

    C++代码:

    struct __AtAutoreleasePool {
      __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
      ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
      void * atautoreleasepoolobj;
    };
    
    int main(int argc, char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
            return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
        }
    }
    

    AutoreleasePoolPage

    objc_autoreleasePoolPush()objc_autoreleasePoolPop()的函数实现我们可以在苹果开源代码objc4NSObject.mm中找到,其实就是直接对AutoreleasePoolPage的调用。

    push

    • AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)
    • AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
    • AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
    • 上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置
    • 一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入
    class AutoreleasePoolPage 
    {
        magic_t const magic;        // 用来校验 AutoreleasePoolPage 的结构是否完整
        id *next;                   // 指向栈顶,也就是最新入栈的autorelease对象的下一个位置
        pthread_t const thread;     // 指向当前线程
        AutoreleasePoolPage * const parent; // 指向父节点
        AutoreleasePoolPage *child; // 指向子节点
        uint32_t const depth;       // 表示链表的深度,也就是链表节点的个数
        uint32_t hiwat;             // 表示high water mark(最高水位标记)
    
        static inline void *push() {
            id *dest;
            if (DebugPoolAllocation) { // Debug模式
                // Each autorelease pool starts on a new pool page.
                dest = autoreleaseNewPage(POOL_BOUNDARY);
            } else {
                dest = autoreleaseFast(POOL_BOUNDARY);
            }
            assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
            return dest;
        }
    
        static inline id *autoreleaseFast(id obj)  {
            AutoreleasePoolPage *page = hotPage();  // 获取当前PoolPage
            if (page && !page->full()) {            // 当前Page存在且未满
                return page->add(obj);              // 把obj添加到当前Page中
            } else if (page) {                      // 当前Page已满
                return autoreleaseFullPage(obj, page);
            } else {                                // 不存在PoolPage
                return autoreleaseNoPage(obj);
            }
        }
    }
    

    pop

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

    AutoReleasePool

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

    1. 根据传入的哨兵对象地址找到哨兵对象所处的page
    2. 在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次- release消息,并向回移动next指针到正确位置
    3. 补充2:从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page
    AutoReleasePool pop后
    static inline void pop(void *token) 
        {
            AutoreleasePoolPage *page;
            id *stop;
    
            if (token == (void*)EMPTY_POOL_PLACEHOLDER) {  // 是否占位池
                // Popping the top-level placeholder pool.
                // 弹出顶层占位池
                if (hotPage()) {
                    // Pool was used. Pop its contents normally.
                    // Pool pages remain allocated for re-use as usual.
                    // 使用了游泳池, 正常弹出内容
                    // 池仍像往常一样分配给重用
                    pop(coldPage()->begin());
                } else {
                    // Pool was never used. Clear the placeholder.
                    // 游泳池从未使用 清除占位符
                    setHotPage(nil);
                }
                return;
            }
    
            page = pageForPointer(token); // 通过哨兵地址获取哨兵对象所在page对象
            stop = (id *)token;
            if (*stop != POOL_BOUNDARY) {
                if (stop == page->begin()  &&  !page->parent) {
                    // Start of coldest page may correctly not be POOL_BOUNDARY:
                    // 1. top-level pool is popped, leaving the cold page in place
                    // 2. an object is autoreleased with no pool
                } else {
                    // Error. For bincompat purposes this is not 
                    // fatal in executables built with old SDKs.
                    return badPop(token);
                }
            }
    
            if (PrintPoolHiwat) printHiwat(); // 记录最高水位标记
    
            page->releaseUntil(stop);  // 把哨兵对象之后入栈的对象进行出栈操作,并对出栈对象发出release消息
    
    
            // memory: delete empty children
            // // 删除空掉的page
            if (DebugPoolAllocation  &&  page->empty()) {
                // special case: delete everything during page-per-pool debugging
                // 在Debug期间进行删除操作
                AutoreleasePoolPage *parent = page->parent;
                page->kill();
                setHotPage(parent);
            } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
                // special case: delete everything for pop(top) 
                // when debugging missing autorelease pools
                // 在不存在autorelease pools的情况删除空的page
                page->kill();
                setHotPage(nil);
            } 
            else if (page->child) {
                // hysteresis: keep one empty child if page is more than half full
                // 如果当前page使用超过一半,就保留一个空的child page
                if (page->lessThanHalfFull()) {
                    page->child->kill();
                }
                else if (page->child->child) {
                    page->child->child->kill();
                }
            }
        }
    

    相关文章

      网友评论

          本文标题:Autorelease

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