手動(dòng)目錄
- 初探autoreleasePool結(jié)構(gòu)
- AutoreleasePoolPage 結(jié)構(gòu)
- 每個(gè)Page能裝多少個(gè)對(duì)象
- push & pop
- 面試題
初探autoreleasePool結(jié)構(gòu)
用clang的方式探索autoreleasePool
int main(int argc, char * argv[]){
@autoreleasepool {
}
return 1;
}
用clang命令:clang -rewrite-objc main.m
之后得到.cpp文件
int main(int argc, char * argv[]){
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
return 1;
}
關(guān)鍵就在{ __AtAutoreleasePool __autoreleasepool; }
,在.cpp 文件中 找到其結(jié)構(gòu)
// 是一個(gè)結(jié)構(gòu)體
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();} // 構(gòu)造函數(shù)
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);} // 析構(gòu)函數(shù)
void * atautoreleasepoolobj;
};
在這個(gè)結(jié)構(gòu)體中,它調(diào)用了一個(gè)objc_autoreleasePoolPush
方法 ,那么它到底干了什么,我們?cè)趏bjc源碼中去看
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push(); // 壓棧
}
class AutoreleasePoolPage : private AutoreleasePoolPageData
AutoreleasePoolPage 結(jié)構(gòu)
找到這里的時(shí)候,我們先來看看AutoreleasePoolPage
的說明
Autorelease pool implementation
A thread's autorelease pool is a stack of pointers.
Each pointer is either an object to release, or POOL_BOUNDARY which is
an autorelease pool boundary.
A pool token is a pointer to the POOL_BOUNDARY for that pool. When
the pool is popped, every object hotter than the sentinel is released.
The stack is divided into a doubly-linked list of pages. Pages are added
and deleted as necessary.
Thread-local storage points to the hot page, where newly autoreleased
objects are stored.
說明里面提到幾點(diǎn):
1、stack
先進(jìn)后出
2荐健、pool boundary
邊界(也有叫哨兵的)冠息,它是一個(gè)對(duì)象觉痛,可以理解為標(biāo)記琴儿。
3、釋放池里存的 要么是邊界嘁捷,要么是需要釋放的對(duì)象造成。
4、是一個(gè)雙向鏈表結(jié)構(gòu)
繼續(xù)跟蹤AutoreleasePoolPage
的父類AutoreleasePoolPageData
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
magic_t const magic; // 檢查校驗(yàn)完整性的變量 16位
__unsafe_unretained id *next; // 指向新加入的autorelease對(duì)象下一個(gè)節(jié)點(diǎn) 8
pthread_t const thread; // 當(dāng)前所在的線程 8
AutoreleasePoolPage * const parent; // 父節(jié)點(diǎn) 指向前一個(gè)page 8
AutoreleasePoolPage *child; // 子節(jié)點(diǎn) 指向下一個(gè)page 8
uint32_t const depth; // 鏈表的深度雄嚣,節(jié)點(diǎn)個(gè)數(shù) 4
uint32_t hiwat; // 數(shù)據(jù)容納的一個(gè)上限 4
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
struct magic_t {
static const uint32_t M0 = 0xA1A1A1A1; // 全局靜態(tài) 在常量區(qū) .data段 不占這里的位數(shù)
# define M1 "AUTORELEASE!"
static const size_t M1_len = 12; // 全局靜態(tài) 在常量區(qū) .data段 不占這里的位數(shù)
uint32_t m[4]; // 4 * 4 = 16 這才是為什么這個(gè)結(jié)構(gòu)體16位
......
};
相關(guān)屬性說明
-
magic
檢查校驗(yàn)完整性的變量 -
next
指向新加入的autorelease對(duì)象下一個(gè)位置 -
thread
page當(dāng)前所在的線程 -
parent
父節(jié)點(diǎn) 指向前一個(gè)page -
child
子節(jié)點(diǎn) 指向下一個(gè)page -
depth
鏈表的深度晒屎,節(jié)點(diǎn)個(gè)數(shù) -
hiwat
數(shù)據(jù)容納的一個(gè)上限
鏈表的結(jié)構(gòu)大概為:
每個(gè)Page能裝多少個(gè)對(duì)象
我們通過源碼來找:
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
begin 從 sizeof(this))開始 (上面計(jì)算了它的大小 56) 到 SIZE結(jié)束
SIZE = PAGE_MIN_SIZE = PAGE_SIZE = 4096喘蟆;
那么能存的對(duì)象數(shù)量是:(4096 - 56)/8 = 505
*
打印來驗(yàn)證
在可運(yùn)行的objc源碼中, 修改setting -> automatic reference counting = NO
int main(int argc, const char * argv[]) {
@autoreleasepool {
for (NSInteger i = 0; i < 5; i ++) {
NSObject *obj = [[NSObject alloc] autorelease];
}
_objc_autoreleasePoolPrint();
return 0;
}
}
// 打印結(jié)果
1objc[65796]: ##############
objc[65796]: AUTORELEASE POOLS for thread 0x1000d5dc0
objc[65796]: 6 releases pending. // 這里說了 添加了6個(gè)
objc[65796]: [0x10181d000] ................ PAGE (hot) (cold)
objc[65796]: [0x10181d038] ################ POOL 0x10181d038
objc[65796]: [0x10181d040] 0x10103d480 NSObject
....
objc[65796]: ##############
// 上面的循環(huán)換成 1 + 504
1objc[66085]: ##############
objc[66085]: AUTORELEASE POOLS for thread 0x1000d5dc0
objc[66085]: 506 releases pending. // 這里說了 506
objc[66085]: [0x101809000] ................ PAGE (full) (cold) // 注意這里: page full(滿了)
objc[66085]: [0x101809038] ################ POOL 0x101809038
objc[66085]: [0x101809040] 0x10104af30 NSObject
....
objc[66085]: [0x101809ff8] 0x10104f6e0 NSObject
objc[66085]: [0x10180b000] ................ PAGE (hot) // 這里也換了一頁
objc[66085]: [0x10180b038] 0x10104f6f0 NSObject
objc[66085]: ##############
KCObjcTest was compiled with optimization - stepping may behave oddly; variables may not be available.
通過更多的打印鼓鲁,我們發(fā)現(xiàn)
每頁可以添加505個(gè)對(duì)象
但是第一頁實(shí)際只加進(jìn)去504蕴轨,因?yàn)榈谝豁撚幸粋€(gè)邊界對(duì)象
。
后面的每頁可以實(shí)際加入505個(gè)對(duì)象
push & pop
先看源碼 如何將對(duì)象加入進(jìn)去
在看源碼之前先了解hotPage是當(dāng)前頁 對(duì)看源碼有幫助
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
// push
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
//autoreleaseFast
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
后面的 add() 骇吭、autoreleaseFullPage()橙弱、autoreleaseNoPage()可以繼續(xù)往下跟。代碼太長燥狰,就不貼出來了
pop
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
pop(void *token) {}
popPage()
// 具體代碼就不貼了棘脐。
壓棧過程 (push)
1、如果有分頁龙致,并且這個(gè)頁還沒有滿蛀缝,直接add
2、如果有分頁目代,并且滿了屈梁,創(chuàng)建一張新頁便加入對(duì)象。
3榛了、如果還沒有頁在讶,創(chuàng)建一個(gè)page 并加入邊界對(duì)象和要加入的對(duì)象。出棧(pop)
1忽冻、通過while循環(huán)真朗,從后往前找到所有page的對(duì)象,并進(jìn)行release操作
2僧诚、刪除所有的空表
autoreleasePool 嵌套
嵌套使用autoreleasePool的情況下遮婶,會(huì)發(fā)生什么?
我們用代碼來打印
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] autorelease];
@autoreleasepool {
NSObject *obj = [[NSObject alloc] autorelease];
@autoreleasepool {
NSObject *obj = [[NSObject alloc] autorelease];
_objc_autoreleasePoolPrint();
}
}
}
return 0;
}
// 打印結(jié)果
objc[70971]: ##############
objc[70971]: AUTORELEASE POOLS for thread 0x1000d5dc0
objc[70971]: 6 releases pending.
objc[70971]: [0x10080c000] ................ PAGE (hot) (cold)
objc[70971]: [0x10080c038] ################ POOL 0x10080c038
objc[70971]: [0x10080c040] 0x100683ca0 NSObject
objc[70971]: [0x10080c048] ################ POOL 0x10080c048
objc[70971]: [0x10080c050] 0x100683e40 NSObject
objc[70971]: [0x10080c058] ################ POOL 0x10080c058
objc[70971]: [0x10080c060] 0x100683b90 NSObject
objc[70971]: ##############
在看另外一種打印
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *objc = [[NSObject alloc] autorelease];
NSLog(@"--------objc = %@",objc);
dispatch_async(dispatch_get_global_queue(0, 0), ^{ // 子線程有一個(gè)隱性的autoreleasepool
@autoreleasepool {
sleep(1);
NSObject *obj = [[NSObject alloc] autorelease];
NSLog(@"+++++++obj = %@",obj);
_objc_autoreleasePoolPrint();
}
});
_objc_autoreleasePoolPrint();
}
sleep(2); // 防止子線程沒有執(zhí)行完
return 0;
}
// 打印結(jié)果
Test[74472:1192459] --------objc = <NSObject: 0x10290d0b0>
objc[74472]: ############## // 第一個(gè)autoleasepool
objc[74472]: AUTORELEASE POOLS for thread 0x1000d5dc0 // 依賴的線程不同
objc[74472]: 2 releases pending.
objc[74472]: [0x10300a000] ................ PAGE (hot) (cold)
objc[74472]: [0x10300a038] ################ POOL 0x10300a038
objc[74472]: [0x10300a040] 0x10290d0b0 NSObject
objc[74472]: ##############
KCObjcTest[74472:1192759] +++++++obj = <NSObject: 0x102804ac0>
objc[74472]: ############## // 第二個(gè)autoleasepool
objc[74472]: AUTORELEASE POOLS for thread 0x70000b669000
objc[74472]: 3 releases pending.
objc[74472]: [0x10380c000] ................ PAGE (hot) (cold)
objc[74472]: [0x10380c038] ################ POOL 0x10380c038 // 一個(gè)隱性autoreleasepool
objc[74472]: [0x10380c040] ################ POOL 0x10380c040 //手動(dòng)添加的autoreleasepool
objc[74472]: [0x10380c048] 0x102804ac0 NSObject
objc[74472]: ##############
- 1湖笨、相同的線程旗扑,嵌套autoreleasePool 用的是相同的page,但是每個(gè)autoreleasepool 都會(huì)添加一個(gè)邊界對(duì)象(哨兵)
- 2慈省、 不同的線程 使用不同的autoreleasePool
面試題
-
1臀防、ARC 下什么樣的對(duì)象由 Autoreleasepool 管理
當(dāng)使用alloc/new/copy/mutableCopy開始的方法進(jìn)行初始化時(shí),會(huì)生成并持有對(duì)象(也就是不需要pool管理边败,系統(tǒng)會(huì)自動(dòng)的幫他在合適位置release)不是通過alloc等出來的對(duì)象 會(huì)加入自動(dòng)釋放池 比如
id obj = [NSMutableArray array];
2袱衷、Autoreleasepool 與 Runloop 的關(guān)系
主線程默認(rèn)為我們開啟 Runloop,Runloop 會(huì)自動(dòng)幫我們創(chuàng)建Autoreleasepool笑窜,并進(jìn)行Push致燥、Pop 等操作來進(jìn)行內(nèi)存管理
-
3、子線程默認(rèn)不會(huì)開啟 Runloop排截,那出現(xiàn) Autorelease 對(duì)象如何處理嫌蚤?不手動(dòng)處理會(huì)內(nèi)存泄漏嗎辐益?
在子線程你創(chuàng)建了 Pool 的話,產(chǎn)生的 Autorelease 對(duì)象就會(huì)交給 pool 去管理脱吱。如果你沒有創(chuàng)建 Pool 智政,但是產(chǎn)生了 Autorelease 對(duì)象,就會(huì)調(diào)用 autoreleaseNoPage 方法箱蝠。在這個(gè)方法中续捂,會(huì)自動(dòng)幫你創(chuàng)建一個(gè) hotpage(hotPage 可以理解為當(dāng)前正在使用的 AutoreleasePoolPage,如果你還是不理解抡锈,可以先看看 Autoreleasepool 的源代碼疾忍,再來看這個(gè)問題 ),并調(diào)用page->add(obj)將對(duì)象添加到 AutoreleasePoolPage 的棧中床三,