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)