由于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)化:
在調(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)然也有特例:
- 當(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ì)被回收
- 當(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ò)