@autoreleasepool的底層實(shí)現(xiàn)

由于markdown會(huì)把兩個(gè)__ 之間的內(nèi)容當(dāng)成粗體,所以下文 __ autoreleasing等詞語(yǔ)會(huì)在 __ 后面加空格

@autoreleasepool本質(zhì)是一個(gè)C++結(jié)構(gòu)體:

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

@autoreleasepool的具體實(shí)現(xiàn):

編譯器會(huì)把

@autoreleasepool{

}

轉(zhuǎn)換成:

void *atautoreleasepoolobj = objc_autoreleasePoolPush();

__AtAutoreleasePool實(shí)例被創(chuàng)建的時(shí)候,把創(chuàng)建的對(duì)象插入數(shù)組,同時(shí)兼顧著AutoreleasePoolPage的管理,詳情查看https://draveness.me/autoreleasepool - objc_autoreleasePoolPush 方法

{}中的代碼

緩存池中創(chuàng)建的對(duì)象,編譯器不會(huì)在對(duì)象所在作用域結(jié)束的時(shí)候插入release消息發(fā)送,而是把release留到__AtAutoreleasePool釋放時(shí),實(shí)現(xiàn)autorelease

objc_autoreleasePoolPop(atautoreleasepoolobj);

__AtAutoreleasePool實(shí)例銷毀的時(shí)候,循環(huán)彈出對(duì)象調(diào)用objc_autorelease把對(duì)象release,理論上來說,傳入其他對(duì)象到objc_autoreleasePoolPop可行的,釋放池會(huì)釋放到傳入的對(duì)象位置然后結(jié)束

autoreleasepool

autoreleasepool的本質(zhì)是一個(gè)隊(duì)列,由AutoreleasePoolPage組成的雙向鏈表,AutoreleasePoolPage內(nèi)部又是一個(gè)類似于數(shù)組的結(jié)構(gòu)

AutoreleasePoolPage

AutoreleasePoolPage是用C++實(shí)現(xiàn)的類,每次被創(chuàng)建都會(huì)占據(jù)4096字節(jié)內(nèi)存(也就是虛擬內(nèi)存一頁(yè)的大小),除了存放下面的成員(放在內(nèi)存低位),其余空間都用來存放@autoreleasepool中添加的對(duì)象,一般在私有成員后會(huì)再緊跟著存放一個(gè)POOL_SENTINEL(詳情看下面)作為AutoreleasePoolPage的begin()

成員:

pthread_t const thread;
    //當(dāng)前指向的線程

id *next;
    //指向當(dāng)前對(duì)象棧的棧頂,如果在pool中對(duì)對(duì)象發(fā)送autorelease,就會(huì)插入到對(duì)象棧頂部(當(dāng)前next指向的位置),如果next指向了對(duì)象棧的end,那么就會(huì)創(chuàng)建新AutoreleasePoolPage

AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
    //雙向鏈表結(jié)構(gòu)

magic_t const magic;
    //用于對(duì)當(dāng)前 AutoreleasePoolPage 完整性的校驗(yàn)

uint32_t const depth;
uint32_t hiwat;

所以objc_autoreleasePoolPush和objc_autoreleasePoolPop只是簡(jiǎn)單包裝AutoreleasePoolPage的操作而已

objc_autoreleasePoolPush會(huì)往最后的page中插入一個(gè)POOL_SENTINEL對(duì)象(哨兵對(duì)象,等價(jià)于nil)并返回其地址指針(也就是上面的atautoreleasepoolobj,下面簡(jiǎn)稱sentine)

objc_autoreleasePoolPop會(huì)從最后一個(gè)page的next開始,不停發(fā)送release直到遇到第一個(gè)sentine為止,之所以說是第一個(gè),是因?yàn)榇嬖谇短椎腶utoreleasepool就會(huì)存在多個(gè)sentine,所以每一層autoreleasepool結(jié)束就只會(huì)釋放最后一個(gè)sentine后面的對(duì)象

ARC下runtime對(duì)autoreleasepool的優(yōu)化:

對(duì)返回值的優(yōu)化:
假設(shè)現(xiàn)在有:

- (instancetype)createSark {
    return [self new];
}
// caller
Sark *sark = [Sark createSark];

按照autoreleasepool的設(shè)計(jì)思想(誰(shuí)創(chuàng)建誰(shuí)釋放的原則),返回值需要是一個(gè)autorelease對(duì)象才能配合調(diào)用方(需要retain)正確管理內(nèi)存,這個(gè)思路下上面的代碼會(huì)被改寫成類似于:

- (instancetype)createSark {
    id tmp = [self new];//這里不會(huì)用objc_retainAutoreleasedReturnValue獲取
    return objc_autoreleaseReturnValue(tmp); // 代替我們調(diào)用autorelease
}
// caller
id tmp = objc_retainAutoreleasedReturnValue([Sark createSark]) // 代替我們調(diào)用retain
Sark *sark = tmp;
objc_storeStrong(&sark, nil); // 相當(dāng)于代替我們調(diào)用了release

但為了減少autoreleasepool的開銷,系統(tǒng)會(huì)稍微取巧地利用TLS進(jìn)行優(yōu)化:

TLS:Thread Local Storage,線程局部存儲(chǔ),將一塊內(nèi)存作為某個(gè)線程專有的存儲(chǔ),以key-value的形式進(jìn)行讀寫

在調(diào)用objc_autoreleaseReturnValue方法時(shí),runtime檢測(cè)到函數(shù)棧中存在objc_retainAutoreleasedReturnValue,就會(huì)將返回值object儲(chǔ)存在TLS中,然后直接返回這個(gè)object(不調(diào)用autorelease);同時(shí),在外部接收這個(gè)返回值的objc_retainAutoreleasedReturnValue里,發(fā)現(xiàn)TLS中正好存了這個(gè)對(duì)象,就直接獲取這個(gè)object(不調(diào)用retain)。

于是乎,調(diào)用方和被調(diào)方利用TLS做中轉(zhuǎn),免去了對(duì)返回值的內(nèi)存管理操作(少了一次autoreleasepool存放,也就少了一次autoreleasepool的retain,返回的地方少了一次release)
這個(gè)優(yōu)化跟C++的右值引用有點(diǎn)類似

與objc_retainAutoreleasedReturnValue相對(duì)的是objc_retain函數(shù) , 因?yàn)槭褂昧薚LS做中轉(zhuǎn) , 所以objc_retainAutoreleasedReturnValue不需要注冊(cè)到autoreleasepool中而返回對(duì)象,也能夠正確地獲取對(duì)象

ARC中基本上所有創(chuàng)建變量的行為都會(huì)使用TLS優(yōu)化(不管是變量是__strong還是__weak都會(huì)),當(dāng)然也有特例:

  1. 當(dāng)遇到alloc/new/copy/mutableCopy前綴的類方法時(shí),編譯器是不會(huì)使用TLS優(yōu)化的,因?yàn)橐赃@些開頭的方法遵循內(nèi)存管理的原則的【自己持有自己生成的對(duì)象】原則,所以編譯器會(huì)為了堅(jiān)持內(nèi)存管理原則,不會(huì)擴(kuò)大它們的作用域,該release就會(huì)release,所以創(chuàng)建變量的地方也不會(huì)通過objc_retainAutoreleasedReturnValue獲取

測(cè)試下來,通過[[Class alloc]init]創(chuàng)建的對(duì)象也不會(huì)進(jìn)行TLS優(yōu)化
alloc等類方法沒有優(yōu)化的結(jié)果是:一般情況下變量在出了作用域就會(huì)被回收

  1. 當(dāng)返回值本身有調(diào)用autorelease方法時(shí),不會(huì)使用return objc_autoreleaseReturnValue(tmp);

比如返回[NSArray new]時(shí)會(huì)調(diào)用objc_autoreleaseReturnValue,而返回[NSArray array]時(shí)會(huì),因?yàn)?array內(nèi)部返回前會(huì)調(diào)用autorelease方法,已經(jīng)放到了autoreleasepool就不需要TLS優(yōu)化了
但如果創(chuàng)建一個(gè)變量(默認(rèn)是__ strong id)緩存,用autorelease的對(duì)象賦值給這個(gè)變量,然后返回這個(gè)變量,還是會(huì)使用objc_autoreleaseReturnValue返回的(因?yàn)開_strong id抵消掉了autorelease帶來的優(yōu)化),如:

+ (id)Object{
    return [NSMutableArray array];

等價(jià)于:    
    id obj = [[[NSMutableArray alloc]init]autorelease];
    return obj;
}

+ (id)Object{
    id  obj = [NSMutableArray array];
    return obj;

等價(jià)于:
    id obj = [[NSMutableArray alloc]init];
    objc_retainAutoreleasedReturnValue(obj);

    objc_retain(obj);
    objc_storeStrong(&obj,nil);//這里相當(dāng)于執(zhí)行release,和前面的objc_retain方法相消,所以這兩行會(huì)被優(yōu)化掉
    return objc_autoreleaseReturnValue(obj);

上面優(yōu)化后等價(jià)于:
    id obj = [[NSMutableArray alloc]init];
    return objc_autoreleaseReturnValue(obj);
}

內(nèi)存管理原則,

任何內(nèi)存行為都至少會(huì)遵循其中一種:

自己生成的對(duì)象,自己所持有。
非自己生成的對(duì)象,自己也能持有。
不需要自己持有的對(duì)象是釋放。
非自己持有的對(duì)象無(wú)法釋放

而上面的+createSark,并不遵循上面的原則所以會(huì)用TLS優(yōu)化
而類似于id obj = [[testObj create] getObj];的寫法,實(shí)際上會(huì)調(diào)用兩次objc_retainAutoreleasedReturnValue
如果不創(chuàng)建obj,即只調(diào)用[[testObj create] getObj];則只有調(diào)用一次

涉及到ARC和MRC混編

系統(tǒng)會(huì)再通過__builtin_return_address這個(gè)內(nèi)建函數(shù)判斷是否有調(diào)用過objc_autoreleaseReturnValue,如果有就是ARC,通過上面的TLS判斷需不需要retain,如果沒有就是MRC,直接retain一次

char *__builtin_return_address(int level)//傳入的level從0開始代表本函數(shù),1代表調(diào)用的函數(shù),以此類推

判斷是否有用TLS優(yōu)化的辦法:

創(chuàng)建對(duì)象后通過_objc_rootRetainCount(obj)函數(shù)查看對(duì)象的計(jì)數(shù)器,一般來說如果為2則代表是TLS優(yōu)化,如果為1則為alloc等方法創(chuàng)建的沒有使用TLS

關(guān)于__autoreleasing

__ autoreleasing 和 __ strong和 __ weak和__ unsafe_unretained是相互對(duì)立的

__ autoreleasing是用在ARC下代替autorelease方法,本質(zhì)是:

__attribute__((objc_ownership(autoreleasing)))

當(dāng)使用__autoreleasing修飾變量時(shí),編譯器會(huì)在變量賦值后進(jìn)行

objc_autorelease(obj);

把這個(gè)變量加到autoreleasepool,把變量的釋放時(shí)機(jī)強(qiáng)制設(shè)定到autoreleasepool結(jié)束(即autoreleasepool持有了該變量),防止編譯器優(yōu)化直接把對(duì)象返回而且沒做release處理(因?yàn)榫幾g器優(yōu)化,拿到這個(gè)變量也不會(huì)進(jìn)行retain,所以就少了一次retain,autoreleasepool會(huì)幫忙retain一次)

也就是說,當(dāng)遇到編譯器進(jìn)行TLS優(yōu)化時(shí),如果想要避免變量出了當(dāng)前作用域就被銷毀的話,前綴一個(gè) __autoreleasing ,把釋放時(shí)機(jī)改到緩存池結(jié)束,就不會(huì)崩了

__ autoreleasing的另一個(gè)作用時(shí)修飾指針的指針,比如最常見的:

NSError **error其實(shí)是NSError * __autoreleasing *error:
-(void)performOperationWithError:(NSError *__autoreleasing *)error;

而平常寫

NSError *error = nil;
BOOL result = [self performOperationWithError:&error];

編譯器會(huì)改寫成:

NSError *error = nil;
NSError __autoreleasing *tmp = error;
BOOL result = [obj performOperationWithError:&tmp];
error = tmp;

所以error指針本身在這時(shí)候也會(huì)變化

之所以傳入指針的指針需要__ autoreleasing只是為了遵循內(nèi)存管理原則而已,寫成:

-(void)performOperationWithError:(NSError * __ strong *)error;

也是可以正常運(yùn)行的,但為了遵循內(nèi)存管理原則還是老老實(shí)實(shí)寫上__ autoreleasing把指針吧

測(cè)試下來,改成__strong后,即使把error作為返回值也不會(huì)崩,作用域結(jié)束后error也能被正常釋放

通過匯編查看也只是對(duì)*error賦值是使用_objc_release而不是_objc_autorelease和傳入&error通過上面的方式改寫而已,只能猜測(cè)是為了內(nèi)存占用才使用__autoreleasing把,畢竟傳入指針的指針這種行為除了傳入一個(gè)NSError之外,更多的是用在循環(huán)遍歷的時(shí)候,比如NSArray的enumerateObjectsUsingBlock方法,系統(tǒng)會(huì)為block加上@autoreleasepool,降低循環(huán)的內(nèi)存使用

ps:

NSError *error = nil;
NSError **pError = &error;

會(huì)報(bào)錯(cuò),因?yàn)閑rror是__ stong的,而 pError是 __ autoreleasing,需要手動(dòng)指明兩個(gè)變量為同一個(gè)類型才不會(huì)報(bào)錯(cuò)

最后編輯于
?著作權(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ù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,237評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,957評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,248評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,356評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,081評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,485評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,534評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,720評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,263評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,025評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,204評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,787評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,461評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,874評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,105評(píng)論 1 289
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,945評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,205評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容