Autorelease

2019-12-25  本文已影响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();
            }
        }
    }
上一篇下一篇

猜你喜欢

热点阅读