autoreleasepool

main 函数可以说是在整个 iOS 开发中非常不起眼的一个函数,却是整个 iOS 应用的入口。

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

@autoreleasepool 结构

通过对下面代码反汇编,

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
    }
    return 0;
}
  FPRuntimeTest`main:
    0x100003f10 <+0>:  pushq  %rbp
    0x100003f11 <+1>:  movq   %rsp, %rbp
    0x100003f14 <+4>:  subq   $0x10, %rsp
    0x100003f18 <+8>:  movl   $0x0, -0x8(%rbp)
    0x100003f1f <+15>: movl   %edi, -0x4(%rbp)
    0x100003f22 <+18>: movq   %rsi, -0x10(%rbp)
    0x100003f26 <+22>: callq  0x100003f48               ; symbol stub for: objc_autoreleasePoolPush
->  0x100003f2b <+27>: movq   %rax, %rdi
    0x100003f2e <+30>: callq  0x100003f42               ; symbol stub for: objc_autoreleasePoolPop
    0x100003f33 <+35>: xorl   %eax, %eax
    0x100003f35 <+37>: addq   $0x10, %rsp
    0x100003f39 <+41>: popq   %rbp
    0x100003f3a <+42>: retq   

可以知道,调用了objc_autoreleasePoolPushobjc_autoreleasePoolPop函数.
在runtime源码中查找

void *objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

调用了 AutoreleasePoolPage 类 的 push()pop()方法

class AutoreleasePoolPage : private AutoreleasePoolPageData
{
    friend struct thread_data_t; // 声明为友元

public:
    static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MIN_SIZE;  // size and alignment, power of 2 // 1左移12位=4096一个自动释放池页的大小,也是一个页大小
#endif
    
private:
    static pthread_key_t const key = AUTORELEASE_POOL_KEY; // 用来从线程中取出来当前释放池的key
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const COUNT = SIZE / sizeof(id);// 存储对象的数量
    static size_t const MAX_FAULTS = 2;

/*
当仅推入一个池且从未包含任何对象时,EMPTY_POOL_PLACEHOLDER就会存储在TLS中。 
当顶层(即libdispatch)推送并弹出池但从不使用它们时,这样可以节省内存。
*/
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)
// 哨兵对象,nil
#   define POOL_BOUNDARY nil
···
}

父类

struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
    struct AutoreleasePoolEntry {
        uintptr_t ptr: 48;
        uintptr_t count: 16;

        static const uintptr_t maxCount = 65535; // 2^16 - 1
    };
    static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endif

    magic_t const magic;
    __unsafe_unretained id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;

    AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
        : magic(), next(_next), thread(_thread),
          parent(_parent), child(nil),
          depth(_depth), hiwat(_hiwat)
    {
    }
};

由上可得,下面的代码可以理解为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;
};
  • magic 用于对当前 AutoreleasePoolPage 完整性的校验
  • thread 保存了当前页所在的线程

parent 和 child 就是用来构造双向链表的指针。

每一个自动释放池都是由一系列的 AutoreleasePoolPage 组成的,并且每一个 AutoreleasePoolPage 的大小都是 4096 字节(16 进制 0x1000)

magic_t
struct magic_t {
    static const uint32_t M0 = 0xA1A1A1A1;
#   define M1 "AUTORELEASE!"
    static const size_t M1_len = 12;
    uint32_t m[4];

    magic_t() {
        assert(M1_len == strlen(M1));//判断M1是否是12个字符长度
        assert(M1_len == 3 * sizeof(m[1]));//判断M1_len是不是12

        m[0] = M0;//将0xA1A1A1A1赋值给M0
        //把M1字符串地址的12个字节复制到m1地址所指向的空间中
        strncpy((char *)&m[1], M1, M1_len);
    }
     //析构函数
    ~magic_t() {
        m[0] = m[1] = m[2] = m[3] = 0;
    }
     //检测函数
    bool check() const {
        return (m[0] == M0 && 0 == strncmp((char *)&m[1], M1, M1_len));
    }
     //快速检测函数
    bool fastcheck() const {
#if CHECK_AUTORELEASEPOOL
        return check();
#else
        return (m[0] == M0);
#endif
    }

#   undef M1
};

push

    static inline void *push() 
    {
        id *dest;
// 判断是不是在Debug模式下,在 Debug 情况下每一个自动释放池都以一个新的poolPage开始会去调用autoreleaseNewPage方法,否则的话就去调用autoreleaseFast函数
        if (slowpath(DebugPoolAllocation)) { 
            // 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;
    }

autoreleaseFast

 static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) { // 有page且page没有存满
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);// 有page,但是存满了
        } else {
            return autoreleaseNoPage(obj); // 没有page
        }
    }

上述方法分三种情况选择不同的代码执行:

  • 有 hotPage 并且当前 page 不满
    -- 调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
  • 有 hotPage 并且当前 page 已满
    -- 调用 autoreleaseFullPage 初始化一个新的页
    -- 调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
  • 无 hotPage
    -- 调用 autoreleaseNoPage 创建一个 hotPage
    -- 调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中

最后的都会调用 page->add(obj) 将对象添加到自动释放池中。

hotPage 可以理解为当前正在使用的 AutoreleasePoolPage。

hotPage()

取出当前的page,如果取出的page是EMPTY_POOL_PLACEHOLDER,那就说明是空的池子,就说明还没有page,直接返回nil
EMPTY_POOL_PLACEHOLDER其实就是当一个释放池没有包含任何对象,又被推入栈中,就存储在TLS(Thread_local_storage)中,叫做空池占位符

我们会发现里面有一个tls_get_direct()函数,其实就是通过这个函数去取出当前的page的,其中tls的意思就是线程局部存储(Thread Local Storage)的缩写。

 static inline AutoreleasePoolPage *hotPage() 
    {
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
            tls_get_direct(key);
        if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        if (result) result->fastcheck();
        return result;
    }
    id *add(id obj)
    {
        ASSERT(!full());
        unprotect();//解除保护
        id *ret;
        ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;//将obj插入到page中并且重新去设置next的指向
        protect();//设置保护
        return ret;
    }

我们主要去看unprotect()方法和protect()方法
这个函数在Linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性,函数原型int mprotect(const void *start, size_t len, int prot); 这个函数的区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。关于这一点在AutoreleasePoolPage类当中也有提及

autoreleaseFullPage

 static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {
        // The hot page is full. 
        // Step to the next non-full page, adding a new page if necessary.
        // Then add the object to that page.
        ASSERT(page == hotPage());//判断是不是最新的page
        ASSERT(page->full()  ||  DebugPoolAllocation);////判断page是不是满了或者是不是Debug模式下

        do {
//判断page->child是不是存在,如果存在的话就让page = page->child
            if (page->child) page = page->child;
//否则就去创建
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);
        return page->add(obj);
    }

setHotPage

    static inline void setHotPage(AutoreleasePoolPage *page) 
    {
        if (page) page->fastcheck();
        tls_set_direct(key, (void *)page);
    }

autoreleaseNoPage

    static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
        // "No page" could mean no pool has been pushed
        // or an empty placeholder pool has been pushed and has no contents yet
        ASSERT(!hotPage()); //判断hotPage()返回的page是否为空,如果为空的话才能继续下去

        bool pushExtraBoundary = false; // 是否要push边界对象
//如果有占位的EMPTY_POOL_PLACEHOLDER,就去设置需要去push一个边界对象
        if (haveEmptyPoolPlaceholder()) {
            // We are pushing a second pool over the empty placeholder pool
            // or pushing the first object into the empty placeholder pool.
            // Before doing that, push a pool boundary on behalf of the pool 
            // that is currently represented by the empty placeholder.
            pushExtraBoundary = true;
        }
        else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
            // We are pushing an object with no pool in place, 
            // and no-pool debugging was requested by environment.
            _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         objc_thread_self(), (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
//如果不是debug模式以及obj是边界对象就去设置空的占位池子也就是   EMPTY_POOL_PLACEHOLDER
        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
            // We are pushing a pool with no pool in place,
            // and alloc-per-pool debugging was not requested.
            // Install and return the empty pool placeholder.
            return setEmptyPoolPlaceholder();
        }

        // We are pushing an object or a non-placeholder'd pool.

        // Install the first page.
// Install the first page. 创建第一个page
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
//设置最新的page
        setHotPage(page);
        
        // Push a boundary on behalf of the previously-placeholder'd pool.
        if (pushExtraBoundary) {
            /*插入一个边界对象会走这里的方法肯定是之前已经设置过   setEmptyPoolPlaceholder也就是说不是在Debug模式下,并且obj不是POOL_BOUNDARY*/

            page->add(POOL_BOUNDARY);
        }
        
        // Push the requested object or pool.
      // 添加一个对象
        return page->add(obj);
    }

pop

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

pop函数里面的参数是调用objc_autoreleasePoolPush()的返回值,返回值是EMPTY_POOL_PLACEHOLDER或一个地址, 地址存储的值是POOL_BOUNDARY

static inline void
    pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;
        //判断token是否是EMPTY_POOL_PLACEHOLDER 
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            //调用hotPage()方法判断当前的页存不存在
            page = hotPage();
            if (!page) {
                // Pool was never used. Clear the placeholder.
                return setHotPage(nil);
            }
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
//先调用coldPage()寻找到最上面的祖宗结点,然后调用begin方法,返回的即可以用于存放对象地址的第一个位置,然后进行pop
            page = coldPage();
            token = page->begin();
        } else {
           //根据token寻找到页所在的地址
            page = pageForPointer(token);
        }

        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) { // 如果token存储的不是nil
            if (stop == page->begin()  &&  !page->parent) { // 传进来的是  EMPTY_POOL_PLACEHOLDER
//最冷页面的开始可能不是POOL_BOUNDARY:
// 1.弹出顶级池,将冷页保留在原处
// 2.一个没有池的对象被自动释放
            } else {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token); 
            }
        }

        if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
            return popPageDebug(token, page, stop);
        }

        return popPage<false>(token, page, stop);
    }

pageForPointer 找到token地址对应的autorelease页

static AutoreleasePoolPage *pageForPointer(uintptr_t p) 
    {
        AutoreleasePoolPage *result;
        uintptr_t offset = p % SIZE;

        ASSERT(offset >= sizeof(AutoreleasePoolPage));

        result = (AutoreleasePoolPage *)(p - offset);
        result->fastcheck();

        return result;
    }

popPage

 template<bool allowDebug>
    static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();

//释放直到遇到stop
        page->releaseUntil(stop);

        // memory: delete empty children
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top)
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }

释放直到遇到stop

    void releaseUntil(id *stop) 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        
        while (this->next != stop) { // while循环
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            AutoreleasePoolPage *page = hotPage(); // 当前页

            // fixme I think this `while` can be `if`, but I can't prove it
            while (page->empty()) { // 如果页变空了,设置父页为当前页
                page = page->parent;
                setHotPage(page);
            }

            page->unprotect(); // 解除保护
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
            AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;

            // create an obj with the zeroed out top byte and release that
            id obj = (id)entry->ptr;
            int count = (int)entry->count;  // grab these before memset
#else
            id obj = *--page->next; // 获取需要释放的对象
#endif
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); // 空间存储的值变成0xA3A3A3A3
            page->protect();

            if (obj != POOL_BOUNDARY) {
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
                // release count+1 times since it is count of the additional
                // autoreleases beyond the first one
                for (int i = 0; i < count + 1; i++) {
                    objc_release(obj);
                }
#else
                objc_release(obj); // 引用计数-1
#endif
            }
        }

        setHotPage(this); // 设置当前页

#if DEBUG
        // we expect any children to be completely empty
        for (AutoreleasePoolPage *page = child; page; page = page->child) {
            ASSERT(page->empty());
        }
#endif
    }

kill

    void kill() 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        AutoreleasePoolPage *page = this;
        while (page->child) page = page->child; // 找到最后一页

        AutoreleasePoolPage *deathptr;
        do {
            deathptr = page;
            page = page->parent;
            if (page) {
                page->unprotect();
                page->child = nil;
                page->protect();
            }
            delete deathptr;
        } while (deathptr != this); // 通过 parent 进行autorelease的释放
    }

autorelease

static inline id autorelease(id obj)
    {
        ASSERT(!obj->isTaggedPointerOrNil());
        id *dest __unused = autoreleaseFast(obj);
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
        ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  (id)((AutoreleasePoolEntry *)dest)->ptr == obj);
#else
        ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
#endif
        return obj;
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容