iOS面试题+基础知识

自动释放池

2020-08-13  本文已影响0人  KB_MORE

Autoreleasepool底层结构

我们知道在main函数中,会创建一个@autoreleasepool {}对象,那么其底层的结构是怎样的呢?

int main(int argc, char * argv[]) {
    @autoreleasepool {
    return 0;
}

我们还是使用clang -rewrite-objc main.m命令,转换为C++代码查看。通过以下代码,

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
int main(int argc, char * argv[]) {

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 


    }

    return 0;
}

我们可以发现转换后@autoreleasepool主要做了以下几点:

AutoreleasePoolPage底层结构

首先来看AutoreleasePoolPage的相关源码,其几个成员变量的含义如下:

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    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)
    {
    }
};

----------------------------------------------------------------------------------

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
#endif
    
private:
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const COUNT = SIZE / sizeof(id);

    // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is 
    // pushed and it has never contained any objects. This saves memory 
    // when the top level (i.e. libdispatch) pushes and pops pools but 
    // never uses them.
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)

#   define POOL_BOUNDARY nil

    // SIZE-sizeof(*this) bytes of contents follow
    ......
}

翻译如下

一个线程的自动释放池是一个指针的堆栈结构。
每个指针代表一个需要释放的对象或者POOL_BOUNDARY(自动释放池边界)
一个 pool token 就是这个 pool 所对应的 POOL_BOUNDARY 的内存地址。当这个 pool 被 pop 的时候,所有内存地址在 pool token 之后的对象都会被 release。
这个堆栈被划分成了一个以 page 为结点的双向链表。pages 会在必要的时候动态地增加或删除。
Thread-local storage(线程局部存储)指向 hot page ,即最新添加的 autoreleased 对象所在的那个 page 。

通过上面对成员变量的解析和上方官方的注释,我们可以知道AutoreleasePoolPage底层结构如下:

AutoreleasePoolPage是以栈为结点通过双向链表的形式组合而成;遵循先进后出规则,整个自动释放池由一系列的AutoreleasePoolPage组成的,而AutoreleasePoolPage是以双向链表的形式连接起来。自动释放池与线程一一对应;每个AutoreleasePoolPage对象占用4096字节内存,其中56个字节用来存放它内部的成员变量,剩下的空间(4040个字节)用来存放autorelease对象的地址。要注意的是第一页只有504个对象,因为在创建page的时候会在next的位置插入1个POOL_SENTINEL。POOL_BOUNDARY为哨兵对象,入栈时插入,出栈时释放对象到此传入的哨兵对象

该图表示AutoreleasePoolPage的双向列表结构

image

该图表示AutoreleasePoolPage的双向列表和栈结构

image

AutoreleasePoolPage::push()原理

首先我们看objc_autoreleasePoolPush的源码,发现其内部就是调用了AutoreleasePoolPage的push()方法。

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

来到AutoreleasePoolPage内部的push()方法,其中slowpath表示很少会走到,是底部的容错处理,所以最终会走到autoreleaseFast方法中

static inline void *push() 
    {
        id *dest;
        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源码,先是调用了hotPage(),hotPage()方法就是用来获得新创建的未满的Page。其内部主要是判断逻辑:

如果当前 Page 存在且未满,走page->add(obj)将 autorelease 对象入栈,即添加到当前 Page 中如果当前 Page 存在但已满,走autoreleaseFullPage,创建一个新的 Page,并将 autorelease 对象添加进去如果当前 Page 不存在,即还没创建过 Page,创建第一个 Page,并将 autorelease 对象添加进去

 static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage(); // 双向链表中的最后一个 Page
        if (page && !page->full()) {// 如果当前 Page 存在且未满
            return page->add(obj);      // 将 autorelease 对象入栈,即添加到当前 Page 中;
        } else if (page) { // 如果当前 Page 存在但已满
            return autoreleaseFullPage(obj, page); // 创建一个新的 Page,并将 autorelease 对象添加进去
        } else {// 如果当前 Page 不存在,即还没创建过 Page
            return autoreleaseNoPage(obj);      // 创建第一个 Page,并将 autorelease 对象添加进去
        }
    }
page->full()

首先我们来看一下,如何判断当前page是否是满状态的。

begin的地址为:Page自己的地址+Page对象的大小56个字节;
end的地址为:Page自己的地址+4096个字节;
empty:判断Page是否为空的条件是next地址是不是等于begin;
full:判断Page是否已满的条件是next地址是不是等于end(栈顶)。

我们知道next指向的是下一个AutoreleasePoolPage中下一个为空的内存地址,新对象会存在next,如果此时next指向end则代表当前AutoreleasePoolPage已满。

id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }

    id * end() {
        return (id *) ((uint8_t *)this+SIZE);
    }

    bool empty() {
        return next == begin();
    }

    bool full() { 
        return next == end();
    }
page->add(obj)

当page没有存满时,会调用此方法,内部的原理非常简单,就是一个压栈的操作,并将next指针指向这个对象的下一个位置,然后将该对象的位置返回。

id *add(id obj)
    {
        assert(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();
        return ret;
    }
autoreleaseFullPage(obj, page)

如果当前 Page 存在但已满,会调用此方法。其内部实现的主要方法就是一个do..while循环,主要实现了一下的逻辑

由于page是链表结构,所以通过循环查找page->child一级级判断是否page->full()如果到最后一个page都是满的,那么就新new一个AutoreleasePoolPage如果有不满的,或者新创建的,调用setHotPage(page)将当前页设置为活跃最后将对象通过page->add压栈

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());
        ASSERT(page->full()  ||  DebugPoolAllocation);

        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

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

当没有page时,会走到此方法,其主要逻辑如下:

先会判断是否有空的自动释放池存在,如果没有会通过setEmptyPoolPlaceholder()生成一个占位符,表示一个空的自动释放池创建第一个Page,设置它为hotPage将一个POOL_BOUNDARY添加进Page中,并返回POOL_BOUNDARY的下一个位置。插入第一个对象

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());

        bool pushExtraBoundary = false;
        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;
        }
        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.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        // Push a boundary on behalf of the previously-placeholder'd pool.
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        
        // Push the requested object or pool.
        return page->add(obj);
    }

AutoreleasePoolPage::pop(ctxt)原理

看完对象入栈的实现,我们再来看一下出栈的实现。
首先pop的入参token即为POOL_BOUNDARY对应在Page中的地址。当销毁自动释放池时,会从从自动释放池的中的最后一个入栈的autorelease对象开始,依次给它们发送一条release消息,直到遇到这个POOL_BOUNDARY,具体的步骤如下:

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);
        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);

        // memory: delete empty children
        if (DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            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
            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();
            }
        }
    }

pageForPointer(token)

该方法,主要是通过token来拿到当前所在的page。主要实现原理是将指针token与页面的大小(4096)取模,可以得到当前指针的偏移量。然后将指针的地址减偏移量便可以得到首地址。即该page的地址

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;
}

page->releaseUntil(stop)

pop()方法中释放autorelease对象的过程在releaseUntil()方法中,下面来看一下这个方法的实现:

void releaseUntil(id *stop) 
    {
        // Not recursive: we donot want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage

        while (this->next != stop) {
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            AutoreleasePoolPage *page = hotPage();

            // fixme I think this `while` can be `if`, but I canot prove it
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }

            page->unprotect();
            id obj = *--page->next;  // next指针是指向最后一个对象的后一个位置,所以需要先减1
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect();

            if (obj != POOL_BOUNDARY) {
                objc_release(obj);
            }
        }

        setHotPage(this);

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

page->kill()

kill方法删除双向链表中的每一个的page,找到当前page的 child 方向尾部 page,然后反向挨着释放并且把其parent节点的 child 指针置空。

void kill() 
{
    // Not recursive: we donot want to blow out the stack 
    // if a thread accumulates a stupendous amount of garbage
    AutoreleasePoolPage *page = this;
    // 找到链表最末尾的page
    while (page->child) page = page->child;

    AutoreleasePoolPage *deathptr;
    // 循环删除每一个page
    do {
        deathptr = page;
        page = page->parent;
        if (page) {
            page->unprotect();
            page->child = nil;
            page->protect();
        }
        delete deathptr;
    } while (deathptr != this);
}

Autoreleasepool嵌套探究

准备:

单个page嵌套

int main(int argc, const char * argv[]) {
    _objc_autoreleasePoolPrint();             // print1
    @autoreleasepool { //r1 = push()
        _objc_autoreleasePoolPrint();         // print2
        NSObject *p1 = [[[NSObject alloc] init] autorelease];
        NSObject *p2 = [[[NSObject alloc] init] autorelease];
        _objc_autoreleasePoolPrint();         // print3
        @autoreleasepool { //r2 = push()
            NSObject *p3 = [[[NSObject alloc] init] autorelease];
            _objc_autoreleasePoolPrint();     // print4
            @autoreleasepool { //r3 = push()
                NSObject *p4 = [[[NSObject alloc] init] autorelease];
                _objc_autoreleasePoolPrint(); // print5
            } //pop(r3)
            _objc_autoreleasePoolPrint();     // print6
        } //pop(r2)
        _objc_autoreleasePoolPrint();         // print7
    } //pop(r1)
    _objc_autoreleasePoolPrint();             // print8
    return 0;
}

打印结果过如下,通过打印结果,我们可以印证上面原理的探索,其主要的进出栈流程如下图所示,且作用域只在@autoreleasepool {}之间,超过之后就全部调用pop释放

图片.png
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 0 releases pending.
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 0 releases pending.
objc[12943]: [0x1]  ................  PAGE (placeholder)
objc[12943]: [0x1]  ################  POOL (placeholder)
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 3 releases pending.
objc[12943]: [0x7f924480b000]  ................  PAGE  (hot) (cold)
objc[12943]: [0x7f924480b038]  ################  POOL 0x7f924480b038
objc[12943]: [0x7f924480b040]    0x600001b34070  NSObject
objc[12943]: [0x7f924480b048]    0x600001b34080  NSObject
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 5 releases pending.
objc[12943]: [0x7f924480b000]  ................  PAGE  (hot) (cold)
objc[12943]: [0x7f924480b038]  ################  POOL 0x7f924480b038
objc[12943]: [0x7f924480b040]    0x600001b34070  NSObject
objc[12943]: [0x7f924480b048]    0x600001b34080  NSObject
objc[12943]: [0x7f924480b050]  ################  POOL 0x7f924480b050
objc[12943]: [0x7f924480b058]    0x600001b34090  NSObject
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 7 releases pending.
objc[12943]: [0x7f924480b000]  ................  PAGE  (hot) (cold)
objc[12943]: [0x7f924480b038]  ################  POOL 0x7f924480b038
objc[12943]: [0x7f924480b040]    0x600001b34070  NSObject
objc[12943]: [0x7f924480b048]    0x600001b34080  NSObject
objc[12943]: [0x7f924480b050]  ################  POOL 0x7f924480b050
objc[12943]: [0x7f924480b058]    0x600001b34090  NSObject
objc[12943]: [0x7f924480b060]  ################  POOL 0x7f924480b060
objc[12943]: [0x7f924480b068]    0x600001b2c030  NSObject
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 5 releases pending.
objc[12943]: [0x7f924480b000]  ................  PAGE  (hot) (cold)
objc[12943]: [0x7f924480b038]  ################  POOL 0x7f924480b038
objc[12943]: [0x7f924480b040]    0x600001b34070  NSObject
objc[12943]: [0x7f924480b048]    0x600001b34080  NSObject
objc[12943]: [0x7f924480b050]  ################  POOL 0x7f924480b050
objc[12943]: [0x7f924480b058]    0x600001b34090  NSObject
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 3 releases pending.
objc[12943]: [0x7f924480b000]  ................  PAGE  (hot) (cold)
objc[12943]: [0x7f924480b038]  ################  POOL 0x7f924480b038
objc[12943]: [0x7f924480b040]    0x600001b34070  NSObject
objc[12943]: [0x7f924480b048]    0x600001b34080  NSObject
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 0 releases pending.
objc[12943]: [0x7f924480b000]  ................  PAGE  (hot) (cold)
objc[12943]: ##############

多个page嵌套

int main(int argc, const char * argv[]) {
    @autoreleasepool { //r1 = push()
        for (int i = 0; i < 600; i++) {
            NSObject *p = [[[NSObject alloc] init] autorelease];
        }
        @autoreleasepool { //r2 = push()
            for (int i = 0; i < 500; i++) {
                NSObject *p = [[[NSObject alloc] init] autorelease];
            }
            @autoreleasepool { //r3 = push()
                for (int i = 0; i < 200; i++) {
                    NSObject *p = [[[NSObject alloc] init] autorelease];
                }
                _objc_autoreleasePoolPrint();
            } //pop(r3)
        } //pop(r2)
    } //pop(r1)
    return 0;
}

可以看到打印结果如下:根据原理的探究,我们知道每个page除了第一页是504个对象外,其他最多存储505个对象,当一个page满了时候,会创建一个新的page,并且每个page之间是以栈为结点通过双向链表的形式组合而成。其主要流程如下图所示

image
objc[69731]: ##############
objc[69731]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[69731]: 1303 releases pending. //当前自动释放池中有1303个对象(3个POOL_BOUNDARY和1300个NSObject实例)
objc[69731]: [0x100806000]  ................  PAGE (full)  (cold) /* 第一个PAGE,full代表已满,cold代表coldPage */
objc[69731]: [0x100806038]  ################  POOL 0x100806038    //POOL_BOUNDARY
objc[69731]: [0x100806040]       0x10182a040  NSObject            //p1
objc[69731]: [0x100806048]       .....................            //...
objc[69731]: [0x100806ff8]       0x101824e40  NSObject            //p504
objc[69731]: [0x102806000]  ................  PAGE (full)         /* 第二个PAGE */
objc[69731]: [0x102806038]       0x101824e50  NSObject            //p505
objc[69731]: [0x102806040]       .....................            //...
objc[69731]: [0x102806330]       0x101825440  NSObject            //p600
objc[69731]: [0x102806338]  ################  POOL 0x102806338    //POOL_BOUNDARY
objc[69731]: [0x102806340]       0x101825450  NSObject            //p601
objc[69731]: [0x102806348]       .....................            //...
objc[69731]: [0x1028067e0]       0x101825d90  NSObject            //p1008
objc[69731]: [0x102804000]  ................  PAGE  (hot)         /* 第三个PAGE,hot代表hotPage */
objc[69731]: [0x102804038]       0x101826dd0  NSObject            //p1009
objc[69731]: [0x102804040]       .....................            //...
objc[69731]: [0x102804310]       0x101827380  NSObject            //p1100
objc[69731]: [0x102804318]  ################  POOL 0x102804318    //POOL_BOUNDARY
objc[69731]: [0x102804320]       0x101827390  NSObject            //p1101
objc[69731]: [0x102804328]       .....................            //...
objc[69731]: [0x102804958]       0x10182b160  NSObject            //p1300
objc[69731]: ##############

@autorelease与RunLoop

@autorelease与RunLoop关系

其中主要的的RunLoop运行流程如下图所示

image

而且通过打印[NSRunLoop currentRunLoop],可以发现其中有_wrapRunLoopWithAutoreleasePoolHandler()代表的相关AutoreleasePool的回调。

<CFRunLoopObserver 0x6000024246e0 [0x7fff8062ce20]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48c1235c), context = <CFArray 0x600001b7afd0 [0x7fff8062ce20]>{type = mutable-small, count = 1, values = (0 : <0x7fc18f80e038>)}}
<CFRunLoopObserver 0x600002424640 [0x7fff8062ce20]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48c1235c), context = <CFArray 0x600001b7afd0 [0x7fff8062ce20]>{type = mutable-small, count = 1, values = (0 : <0x7fc18f80e038>)}}

那么,RunLoop和AutoreleasePool的主要关系如下

main函数变化分析

了解了他们之间的关系,我们可以通过main函数,来分析一下

// Xcode 11
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
--------------------------------------------------------------------------------
// Xcode 旧版本
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

我们知道@autoreleasepool {}的作用域只在其大括号之间,而且UIApplicationMain主线程会创建主RunLoop,通过上面的探究,我们知道在创建RunLoop的时候,也会对应的创建AutoreleasePool。其中使用autorelease修饰的对象都会添加到RunLoop创建的自动释放池中。

所以Xcode 11和之前版本的区别,主要就是Xcode 11将@autoreleasepool {}提前,这可以保证@autoreleasepool中的autorelease对象在程序启动后立即释放。而之前的版本是在主线程RunLoop创建的自动释放池的外层的,意味着程序结束后main函数中的@autoreleasepool中的autorelease对象才会释放。

总结

  1. 调用了hotPage()获得新创建的未满的Page
  2. 当前 Page 存在且未满,走page->add(obj),将 autorelease 对象入栈,并将next指针指向这个对象的下一个位置,然后将该对象的位置返回
  3. 当前 Page 存在但已满,走autoreleaseFullPage,循环查找page->child并判断是否已满,都已满则创建新的AutoreleasePoolPage,并将 autorelease 对象入栈,设置HotPage
  4. 当没有page时,走autoreleaseNoPage,先会判断是否有空的自动释放池存在并生成占位符,然后创建一个新page并设置HotPage,依次插入POOL_BOUNDARY和autorelease 对象入栈
  1. 判断token是不是EMPTY_POOL_PLACEHOLDER,是的话就清空这个自动释放池
  2. 如果不是的话,就通过pageForPointer(token)拿到token所在的Page
  3. 通过page->releaseUntil(stop)将自动释放池中的autorelease对象全部释放,传参stop即为POOL_BOUNDARY的地址
  4. 判断当前Page是否有子Page,有的话就销毁

kCFRunLoopEntry:在即将进入RunLoop时,会自动创建一个__AtAutoreleasePool结构体对象,并调用objc_autoreleasePoolPush()函数。
kCFRunLoopBeforeWaiting:在RunLoop即将休眠时,会自动销毁一个__AtAutoreleasePool对象,调用objc_autoreleasePoolPop()。然后创建一个新的__AtAutoreleasePool对象,并调用objc_autoreleasePoolPush()。
kCFRunLoopBeforeExit,在即将退出RunLoop时,会自动销毁最后一个创建的__AtAutoreleasePool对象,并调用objc_autoreleasePoolPop()。

转载链接 https://zhuanlan.zhihu.com/p/150569825

上一篇 下一篇

猜你喜欢

热点阅读