objective-C-AutoReleasePool底層實(shí)現(xiàn)

1、首先我們先寫個(gè)段最簡(jiǎn)單的代碼

https://opensource.apple.com/tarballs/objc4/

main.m
int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        for (int i=0; i<10; i++) {
            @autoreleasepool {
                NSString *str = [NSString stringWithFormat:@"%@",@"test"];
                NSLog(@"%@",str);
            }
        }
        
    }
    return 0;
}

這里我們有2個(gè)autoreleasepool嵌套

2、在命令行使用 clang -rewrite-objc main.m 命令把OC代碼轉(zhuǎn)成c++代碼

main.cpp
int main(int argc, const char * argv[]) {

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        for (int i=0; i<10; i++) {
            /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
                NSString *str = ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_j3_vbzlcc353pb72wr4jq14tq180000gn_T_main_b4d708_mi_0, (NSString *)&__NSConstantStringImpl__var_folders_j3_vbzlcc353pb72wr4jq14tq180000gn_T_main_b4d708_mi_1);
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_j3_vbzlcc353pb72wr4jq14tq180000gn_T_main_b4d708_mi_2,str);
            }
        }

    }
    return 0;
}

這里我們可以看到 @autoreleasepool 轉(zhuǎn)成了 __AtAutoreleasePool __autoreleasepool;

??? 定義個(gè)__AtAutoreleasePool變量就可以實(shí)現(xiàn)autoreleasepool

3、我們?cè)賮?lái)看看__AtAutoreleasePool的定義

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

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

看到這里終于明白了,這里定義struct __AtAutoreleasePool變量的時(shí)候構(gòu)造函數(shù)里會(huì)調(diào)用objc_autoreleasePoolPush函數(shù),離開有效區(qū)域時(shí)變量被釋放析構(gòu)函數(shù)里調(diào)用了objc_autoreleasePoolPop,所有這里相當(dāng)于轉(zhuǎn)換成了

int main(int argc, const char * argv[]) {
    
    {
        __AtAutoreleasePool __pool1; 
        __pool1.atautoreleasepoolobj = objc_autoreleasePoolPush();
        for (int i=0; i<10; i++) {
            {
                __AtAutoreleasePool __pool2; 
                __pool2.atautoreleasepoolobj = objc_autoreleasePoolPush();
                NSString *str = [NSString stringWithFormat:@"%@",@"test"];
                NSLog(@"%@",str);
                objc_autoreleasePoolPop(__pool2.atautoreleasepoolobj);   
            }
        }
    
        objc_autoreleasePoolPop(__pool1.atautoreleasepoolobj);    
    }
    return 0;
}

現(xiàn)在我們明白了,這里push/pop使用了一個(gè)堆棧結(jié)構(gòu)

4、objc_autoreleasePoolPush/objc_autoreleasePoolPop做了什么呢,我們來(lái)看看蘋果開源的objc源碼

//  objc/source/NSObject.mm
void *
objc_autoreleasePoolPush(void)
{
    if (UseGC) return nil;
    return AutoreleasePoolPage::push();
}

void
objc_autoreleasePoolPop(void *ctxt)
{
    if (UseGC) return;

    // fixme rdar://9167170
    if (!ctxt) return;

    AutoreleasePoolPage::pop(ctxt);
}

這里這對(duì)方法調(diào)用了AutoreleasePoolPage類的靜態(tài)方法push/pop,我們先來(lái)看看push都做了什么

static inline void *push() 
    {
        //這里push就調(diào)用了AutoreleasePoolPage的autoreleaseFast成員,
        //并傳遞了一個(gè)POOL_SENTINEL(#define POOL_SENTINEL nil)
        //這里可以看字面意思傳遞的是個(gè)值為nil的哨兵,也就是一個(gè)哨兵記錄著棧里保存了一個(gè)新的分段
        //從下面的函數(shù)中可以看出返回值是POOL_SENTINEL在棧中的地址,
        //用于pop的時(shí)候把push和pop之前添加的autorelease成員全部清理掉
        id *dest = autoreleaseFast(POOL_SENTINEL);
        
        assert(*dest == POOL_SENTINEL);
        return dest;
    }

static inline id *autoreleaseFast(id obj)
    {
        //hotPage調(diào)用tls_get_direct()方法獲取當(dāng)前激活的AutoreleasePoolPage
        //這里每個(gè)線程的所有autorelease變量全部存儲(chǔ)在AutoreleasePoolPage的雙鏈表中,
        //第一次添加autorelease變量時(shí)先創(chuàng)建一個(gè)AutoreleasePoolPage,
        //一個(gè)AutoreleasePoolPage使用SIZE個(gè)字節(jié),
        //重寫new申請(qǐng)的時(shí)候使用malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);按照SIZE對(duì)齊剛好是申請(qǐng)了一個(gè)內(nèi)存分頁(yè)
        AutoreleasePoolPage *page = hotPage();

        //判斷當(dāng)前激活A(yù)utoreleasePoolPage存在并且沒滿
        if (page && !page->full()) {
            //直接在當(dāng)前分頁(yè)中添加一個(gè)autorelease對(duì)象
            return page->add(obj);
        } else if (page) {//如果當(dāng)前分頁(yè)存在并且滿了
            //創(chuàng)建一個(gè)新的分頁(yè)設(shè)為激活并添加autorelease對(duì)象
            return autoreleaseFullPage(obj, page);
        } else {//如果當(dāng)前沒有激活的分頁(yè)
            //創(chuàng)建分頁(yè)設(shè)為激活并且添加autorelease對(duì)象
            return autoreleaseNoPage(obj);
        }
    }
    

這類總結(jié)下push的時(shí)候我們獲取當(dāng)前激活的AutoreleasePoolPage分頁(yè),當(dāng)前激活的不存在或滿了就新申請(qǐng)個(gè)AutoreleasePoolPage分頁(yè),并且將當(dāng)前分頁(yè)設(shè)置為激活狀態(tài),再把POOL_SENTINEL當(dāng)做哨兵push到棧頂,

下面我們?cè)賮?lái)看看AutoreleasePoolPage的pop方法做了什么

//這里的token就是push對(duì)應(yīng)的POOL_SENTINEL哨兵返回的內(nèi)存地址
static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

        if (token) {
            //1、根據(jù)上個(gè)哨兵內(nèi)存地址找出哨兵所在的AutoreleasePoolPage
            page = pageForPointer(token);
            //標(biāo)記哨兵所在位置
            stop = (id *)token;
            assert(*stop == POOL_SENTINEL);
        } else {
            //2、如果token不存在,直接找到鏈表第一個(gè)AutoreleasePoolPage
            page = coldPage();
            assert(page);
            //標(biāo)記第一個(gè)頁(yè)的棧底位置
            stop = page->begin();
        }

        if (PrintPoolHiwat) printHiwat();

        //3、鏈表從后向前,把堆棧中從記錄的哨兵位置到鏈表最后一個(gè)頁(yè),也就是激活頁(yè)的棧頂分別用objc_release(obj)釋放
        page->releaseUntil(stop);
    }

5、總結(jié)下

①一個(gè)線程的autoreleasepool存儲(chǔ)結(jié)構(gòu)是由AutoreleasePoolPage組成的雙向鏈表
②早起版本每個(gè)AutoreleasePoolPage是一個(gè)內(nèi)存分頁(yè),包含成員和一個(gè)stack

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

③鏈表的所有AutoreleasePoolPage的stack加起來(lái)其實(shí)就是一個(gè)總的stack,分頁(yè)是為了優(yōu)化內(nèi)存
④每次push在stack中添加一個(gè)值為nil的哨兵,并返回這個(gè)哨兵所在位置
⑤每次pop從stack棧頂開始一個(gè)一個(gè)把棧頂元素彈出并調(diào)用objc_release(obj)釋放,直到遇到傳入的哨兵才停止
⑥autoreleasepool嵌套使用stack實(shí)現(xiàn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。