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

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

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

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

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

編譯器會把

@autoreleasepool{

}

轉(zhuǎn)換成:

void *atautoreleasepoolobj = objc_autoreleasePoolPush();

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

{}中的代碼

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

objc_autoreleasePoolPop(atautoreleasepoolobj);

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

autoreleasepool

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

AutoreleasePoolPage

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

成員:

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

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

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

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

uint32_t const depth;
uint32_t hiwat;

所以objc_autoreleasePoolPush和objc_autoreleasePoolPop只是簡單包裝AutoreleasePoolPage的操作而已

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

objc_autoreleasePoolPop會從最后一個page的next開始,不停發(fā)送release直到遇到第一個sentine為止,之所以說是第一個,是因為存在嵌套的autoreleasepool就會存在多個sentine,所以每一層autoreleasepool結(jié)束就只會釋放最后一個sentine后面的對象

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

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

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

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

- (instancetype)createSark {
    id tmp = [self new];//這里不會用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)會稍微取巧地利用TLS進行優(yōu)化:

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

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

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

與objc_retainAutoreleasedReturnValue相對的是objc_retain函數(shù) , 因為使用了TLS做中轉(zhuǎn) , 所以objc_retainAutoreleasedReturnValue不需要注冊到autoreleasepool中而返回對象,也能夠正確地獲取對象

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

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

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

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

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

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

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

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

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

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

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

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

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

自己生成的對象,自己所持有。
非自己生成的對象,自己也能持有。
不需要自己持有的對象是釋放。
非自己持有的對象無法釋放

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

涉及到ARC和MRC混編

系統(tǒng)會再通過__builtin_return_address這個內(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)建對象后通過_objc_rootRetainCount(obj)函數(shù)查看對象的計數(shù)器,一般來說如果為2則代表是TLS優(yōu)化,如果為1則為alloc等方法創(chuàng)建的沒有使用TLS

關(guān)于__autoreleasing

__ autoreleasing 和 __ strong和 __ weak和__ unsafe_unretained是相互對立的

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

__attribute__((objc_ownership(autoreleasing)))

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

objc_autorelease(obj);

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

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

__ autoreleasing的另一個作用時修飾指針的指針,比如最常見的:

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

而平常寫

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

編譯器會改寫成:

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

所以error指針本身在這時候也會變化

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

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

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

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

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

ps:

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

會報錯,因為error是__ stong的,而 pError是 __ autoreleasing,需要手動指明兩個變量為同一個類型才不會報錯

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容