首先是一個(gè)常規(guī)問(wèn)題色迂,autorelease對(duì)象何時(shí)釋放香缺?答:在AutoreleasePoolPage pop的時(shí)候釋放,在主線程的runloop中歇僧,有兩個(gè)oberserver負(fù)責(zé)創(chuàng)建和清空autoreleasepool图张,詳情可以看YY的深入理解runloop。那么子線程呢诈悍?子線程的runloop都需要手動(dòng)開(kāi)啟祸轮,那么子線程中使用autorelease對(duì)象會(huì)內(nèi)存泄漏嗎,如果不會(huì)又是什么時(shí)候釋放呢侥钳。
帶著這個(gè)問(wèn)題适袜,我們看一看runloop的原始碼中答案的答案。
在MRC下舷夺,使用__autoreleasing修飾符等同于MRC下調(diào)用自動(dòng)發(fā)布方法苦酱,所以在NSObject子系統(tǒng)中找到-(id)autorelese方法開(kāi)始看售貌。
1
2
3
4
-(id)autorelease
{
? ? return _objc_rootAutorelease(self);
}
可以看到這個(gè)方法里只是簡(jiǎn)單的調(diào)了一下_objc_rootAutorelease(),繼續(xù)跟進(jìn)疫萤。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
id
_objc_rootAutorelease(id obj)
{
? ? assert(obj);
? ? 返回obj-> rootAutorelease();
}
...
//基本自動(dòng)發(fā)布實(shí)現(xiàn)趁矾,忽略替代拐迁。
內(nèi)聯(lián)ID
objc_object :: rootAutorelease()
{
? ? 如果(isTaggedPointer())返回(id)
? ? 如果(prepareOptimizedReturn(ReturnAtPlus1))返回(id)
? ? 返回rootAutorelease2();
}
...
__attribute __((noinline定硝,used))
id
objc_object :: rootAutorelease2()
{
? ? assert(滓玖!isTaggedPointer());
? ? 返回AutoreleasePoolPage :: autorelease((id)this);
}
檢查是否AutoreleasePoolPage :: autorelease是標(biāo)簽指針和是否要做不加入autoreleasepool的優(yōu)化,然后rootAutorelease2()帝际。最后走入了AutoreleasePoolPage::autorelease()。
接下來(lái)看看AutoreleasePoolPage這個(gè)類饶辙,有關(guān)這個(gè)類的說(shuō)明蹲诀,可以看看sunny的黑幕背后的Autorelease。現(xiàn)在來(lái)看看AutoreleasePoolPage中的實(shí)現(xiàn)弃揽。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public:
? ? 靜態(tài)內(nèi)聯(lián)ID autorelease(id obj)
? ? {
? ? ? ? assert(obj);
? ? ? ? assert(脯爪!obj-> isTaggedPointer());
? ? ? ? id * dest __unused = autoreleaseFast(obj);
? ? ? ? assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || * dest == obj);
? ? ? ? 返回obj;
? ? }
? ? ...
? ? 靜態(tài)內(nèi)聯(lián)ID * autoreleaseFast(id obj)
? ? {
? ? ? ? AutoreleasePoolPage * page = hotPage();
? ? ? ? if(page &&矿微!page-> full()){
? ? ? ? ? ? 返回頁(yè)面-> add(obj);
? ? ? ? } else if(page){
? ? ? ? ? ? 返回autoreleaseFullPage(obj痕慢,page);
? ? ? ? } else {
? ? ? ? ? ? return autoreleaseNoPage(obj);
? ? ? ? }
? ? }
? ? ...
? ? ? ? 靜態(tài)__attribute __((noinline))
? ? id * autoreleaseNoPage(id obj)
? ? {
? ? ? ? //“無(wú)頁(yè)面”可能意味著沒(méi)有推送池
? ? ? ? //或推送了一個(gè)空的占位符池且尚無(wú)內(nèi)容
? ? ? ? assert(!hotPage ())涌矢;
? ? ? ? bool pushExtraBoundary = false;
? ? ? ? if(haveEmptyPoolPlaceholder()){
? ? ? ? ? ? //我們將第二個(gè)池推入空的占位符池
? ? ? ? ? ? //或?qū)⒌谝粋€(gè)對(duì)象推入空的占位符池掖举。
? ? ? ? ? ? //在此之前,代表
? ? ? ? ? ? 當(dāng)前由空占位符表示的池// 推入池邊界娜庇。
? ? ? ? ? ? pushExtraBoundary = true;
? ? ? ? }
? ? ? ? 否則if(obj塔次!= POOL_BOUNDARY && DebugMissingPools){
? ? ? ? ? ? //我們正在推送一個(gè)沒(méi)有池的對(duì)象,
? ? ? ? ? ? //環(huán)境請(qǐng)求了無(wú)池調(diào)試名秀。
? ? ? ? ? ? _objc_inform(“ MISSING POOLS:(%p)類%s的對(duì)象%p”
? ? ? ? ? ? ? ? ? ? ? ? “自動(dòng)釋放励负,沒(méi)有池-”
? ? ? ? ? ? ? ? ? ? ? ? “只是泄漏-中斷”
? ? ? ? ? ? ? ? ? ? ? ? “要調(diào)試的objc_autoreleaseNoPool()”,
? ? ? ? ? ? ? ? ? ? ? ? pthread_self()匕得,(void *)obj继榆,object_getClassName(obj));
? ? ? ? ? ? objc_autoreleaseNoPool(obj);
? ? ? ? ? ? 返回零耗跛;
? ? ? ? }
? ? ? ? 否則裕照,如果(obj == POOL_BOUNDARY &&!DebugPoolAllocation){
? ? ? ? ? ? //我們?cè)跊](méi)有池的情況下推送一個(gè)池调塌,
? ? ? ? ? ? //并且不請(qǐng)求每個(gè)池分配調(diào)試晋南。
? ? ? ? ? ? //安裝并返回空池占位符。
? ? ? ? ? ? 返回setEmptyPoolPlaceholder();
? ? ? ? }
? ? ? ? //我們正在推送對(duì)象或非占位符的池羔砾。
? ? ? ? //安裝首頁(yè)负间。
? ? ? ? AutoreleasePoolPage * page =新的AutoreleasePoolPage(nil);
? ? ? ? setHotPage(page);? ? ? ? //代表先前占位符的池推邊界偶妖。? ? ? ? 如果(pushExtraBoundary){? ? ? ? ? ? page-> add(POOL_BOUNDARY);? ? ? ? }? ? ? ? //推送請(qǐng)求的對(duì)象或池。? ? ? ? 返回頁(yè)面->添加(obj);? ? }
這里我們找到了我們想看的代碼政溃,如果當(dāng)前線程沒(méi)有AutorelesepoolPage的話趾访,代碼執(zhí)行順序?yàn)閍utorelease-> autoreleaseFast-> autoreleaseNoPage。
在autoreleaseNoPage方法中董虱,會(huì)創(chuàng)建一個(gè)hotPage扼鞋,然后調(diào)用page-> add(obj)。也就是說(shuō)甚至這個(gè)線程沒(méi)有AutorelesepoolPage愤诱,使用了autorelease對(duì)象時(shí)也會(huì)new一個(gè)AutoreleasepoolPage出來(lái)管理autorelese對(duì)象云头,不用擔(dān)心內(nèi)存泄漏。
明確了何時(shí)創(chuàng)建autoreleasepool之后就自然而然的有下一個(gè)問(wèn)題淫半,這個(gè)autoreleasepool何時(shí)清空溃槐?
對(duì)于這個(gè)問(wèn)題,這里使用watchpoint set variable命令來(lái)觀察科吭。
首先是一個(gè)最簡(jiǎn)單的場(chǎng)景昏滴,創(chuàng)建一個(gè)子線程。
1
2
3
4
__weak id obj;
...
[NSThread detachNewThreadSelector:@selector(createAndConfigObserverInSecondaryThread)toTarget:self withObject:nil];
使用一個(gè)弱指針觀察子線程中的自動(dòng)釋放對(duì)象对人,子線程中執(zhí)行的任務(wù)谣殊。
1
2
3
4
5
6
7
-(void)createAndConfigObserverInSecondaryThread {
? ? __autoreleasing id test = [NSObject new];
? ? NSLog(@“ obj =%@”,測(cè)試);
? ? obj =測(cè)試牺弄;
? ? [[NSThread currentThread] setName:@“ test runloop thread”]蟹倾;
? ? NSLog(@“線程結(jié)束”);
}
在該obj =測(cè)試位置設(shè)置斷點(diǎn)使用watchpoint set variable obj命令觀察obj,可以看到obj在釋放時(shí)的方法調(diào)用棧是這樣的猖闪。通過(guò)這個(gè)調(diào)用椣侍模可以看到釋放的時(shí)機(jī)在_pthread_exit。這個(gè)方法如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
靜態(tài)無(wú)效tls_dealloc(void * p)
{
? ? if(p ==(void *)EMPTY_POOL_PLACEHOLDER){
? ? ? ? //這里沒(méi)有要清理的對(duì)象或池頁(yè)面培慌。
? ? ? ? 返回;
? ? }
? ? //恢復(fù)工作時(shí)的TLS值
? ? setHotPage((AutoreleasePoolPage *)p);
? ? 如果(AutoreleasePoolPage * page = coldPage()){
? ? ? ? if(豁陆!page-> empty())pop(page-> begin()); //
? ? ? ? 如果(DebugMissingPools || DebugPoolAllocation){
? ? ? ? ? ? // pop()已殺死所有頁(yè)面,則彈出所有池
? ? ? ? } else {
? ? ? ? ? ? page-> kill(); //釋放所有頁(yè)面
? ? ? ? }
? ? }? ? //清除TLS值吵护,以便TLS銷毀不會(huì)循環(huán)? ? setHotPage(nil); }
在這找到了if (!page->empty()) pop(page->begin());這句關(guān)鍵代碼盒音。再往上看一點(diǎn),在_pthread_exit時(shí)會(huì)執(zhí)行下面這個(gè)函數(shù)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void
_pthread_tsd_cleanup(pthread_t self)
{
#if馅而!VARIANT_DYLD
int j;
//首先
為(j = 0; j <PTHREAD_DESTRUCTOR_ITERATIONS; j ++)清理動(dòng)態(tài)鍵{
pthread_key_t k;
for(k = __pthread_tsd_start; k <= self-> max_tsd_key; k ++){_
pthread_tsd_cleanup_key(self祥诽,k);
}
}
self-> max_tsd_key = 0;
//清理
(j = 0; j <PTHREAD_DESTRUCTOR_ITERATIONS; j ++)的靜態(tài)鍵{
pthread_key_t k;
for(k = __pthread_tsd_first; k <=
__pthread_tsd_max ; k ++){_ pthread_tsd_cleanup_key(self,k);
}
}
#endif //瓮恭!VARIANT_DYLD
}
該線程在退出時(shí)會(huì)釋放自身資源雄坪,這個(gè)操作就包含了銷毀自動(dòng)釋放池,在tls_delloc中屯蹦,執(zhí)行了pop操作维哈。
這個(gè)實(shí)驗(yàn)本該到此就結(jié)束了绳姨,對(duì)于文章開(kāi)始的問(wèn)題在這里也已經(jīng)有了答案,線程在銷毀時(shí)會(huì)清空autoreleasepool阔挠。但是上述這個(gè)示例中的線程并沒(méi)有加入runloop飘庄,只是一個(gè)一次性的線程。現(xiàn)在給這個(gè)線程加入runloop來(lái)看看效果會(huì)是怎么樣的购撼。
對(duì)于runloop跪削,我們知道runloop一定要有源能力保證run起來(lái)以后不立即結(jié)束,而source有三種迂求,自定義源切揭,端口源,計(jì)時(shí)器锁摔。
先加一個(gè)計(jì)時(shí)器試試
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-(void)createAndConfigObserverInSecondaryThread {
? ? [[NSThread currentThread] setName:@“ test runloop thread”];
? ? NSRunLoop * loop = [NSRunLoop currentRunLoop];
? ? CFRunLoopObserverRef觀察器;
? ? 觀察者= CFRunLoopObserverCreate(CFAllocatorGetDefault()哼审,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? kCFRunLoopAllActivities谐腰,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? true,//重復(fù)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 0xFFFFFF涩盾,// CATransaction(2000000)之后
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? YYRunLoopObserverCallBack十气,NULL);
? ? CFRunLoopRef cfrunloop = [循環(huán)getCFRunLoop];
? ? 如果(觀察者){? ? ? ? CFRunLoopAddObserver(cfrunloop,觀察者春霍,kCFRunLoopCommonModes);
? ? ? ? CFRelease(觀察者);
? ? }
? ? [NSTimer scheduleTimerWithTimeInterval:5目標(biāo):自我選擇器:@selector(testAction)userInfo:無(wú)重復(fù):是]砸西;
? ? [循環(huán)運(yùn)行];
? ? NSLog(@“線程結(jié)束”);
}
-(void)testAction {
? ? __autoreleasing id test = [NSObject new];
? ? obj =測(cè)試址儒;
? ? NSLog(@“ obj =%@”芹枷,obj);
}
這里的oberserver沒(méi)有什么,就是從YYKit里復(fù)制出來(lái)的一段觀察者代碼莲趣,用于監(jiān)視r(shí)unloop的狀態(tài)鸳慈。發(fā)現(xiàn)的可以看看。
在testAction()中加上watchpoint斷點(diǎn)喧伞,觀察obj的釋放時(shí)機(jī)走芋。可以看到釋放的時(shí)機(jī)在CFRunloopRunSpecific中潘鲫,也就是runloop切換狀態(tài)的時(shí)候翁逞,繼續(xù)往上看發(fā)現(xiàn)這個(gè)替代。這個(gè)函數(shù)中的實(shí)現(xiàn)如下
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__()
1
2
3
4
5
6
靜態(tài)void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION __(CFRunLoopTimerCallBack func溉仑,CFRunLoopTimerRef timer挖函,void * info){
? ? if(func){
? ? ? ? func(timer,info);
? ? }
? ? asm __volatile __(“”); //阻止尾部調(diào)用優(yōu)化
}
這個(gè)所謂func的callback是timer的一個(gè)屬性浊竟,根據(jù)這個(gè)調(diào)用椗不看到浅萧,釋放autoreleasepool的操作應(yīng)該是在這個(gè)callback中。這里猜測(cè)一下timer哲思,應(yīng)該是在自己的回調(diào)函數(shù)里插入了釋放autorelesepool的代碼洼畅。
然后用自己實(shí)現(xiàn)的source試一試,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
-(void)createAndConfigObserverInSecondaryThread {
? ? __autoreleasing id test = [NSObject new];
? ? NSLog(@“ obj =%@”棚赔,測(cè)試);
? ? obj =測(cè)試帝簇;
? ? [[NSThread currentThread] setName:@“ test runloop thread”];
? ? NSRunLoop * loop = [NSRunLoop currentRunLoop];
? ? CFRunLoopObserverRef觀察器靠益;
? ? 觀察者= CFRunLoopObserverCreate(CFAllocatorGetDefault()丧肴,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? kCFRunLoopAllActivities,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? true胧后,//重復(fù)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 0xFFFFFF芋浮,// CATransaction(2000000)之后
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? YYRunLoopObserverCallBack,NULL);
? ? CFRunLoopRef cfrunloop = [循環(huán)getCFRunLoop];
? ? 如果(觀察者){? ? ? ? CFRunLoopAddObserver(cfrunloop壳快,觀察者纸巷,kCFRunLoopCommonModes);? ? ? ? CFRelease(觀察者);? ? }? ? CFRunLoopSourceRef源;? ? CFRunLoopSourceContext sourceContext = {0眶痰,(__bridge void *)(self)瘤旨,NULL,NULL竖伯,NULL存哲,NULL,NULL七婴,NULL祟偷,NULL,NULL打厘,&runLoopSourcePerformRoutine};? ? 源= CFRunLoopSourceCreate(NULL肩袍,0,&sourceContext);? ? CFRunLoopAddSource(cfrunloop婚惫,source氛赐,kCFRunLoopDefaultMode);? ? runLoopSource =源;? ? runLoop = cfrunloop;? ? [循環(huán)運(yùn)行];? ? NSLog(@“線程結(jié)束”); }
-(void)wakeupSource {
? ? //通知輸入源
? ? CFRunLoopSourceSignal(runLoopSource);
? ? //喚醒runLoop
? ? CFRunLoopWakeUp(runLoop); }
...
void runLoopSourcePerformRoutine(void * info)
{
? __autoreleasing id test = [NSObject new];
? ? obj =測(cè)試先舷;
? ? //
? ? NSLog(@“ obj is%@”艰管,obj); // //如果不對(duì)obj賦值,obj會(huì)一直持有createAndConfigObserverInSecondaryThread函數(shù)入口的那個(gè)對(duì)象蒋川,那個(gè)對(duì)象不在這里面的自動(dòng)釋放池影響牲芋。
? ? NSLog(@“”方法%@“,[NSThread currentThread]);
}
這里wakeupSource()是一個(gè)按鈕的點(diǎn)擊事件,用于喚醒runloop缸浦。runloop喚醒之后將執(zhí)行runLoopSourcePerformRoutine函數(shù)夕冲,在runLoopSourcePerformRoutine()中觀察對(duì)象的釋放時(shí)機(jī),發(fā)現(xiàn)是在[NSRunloop run:beforeDate:]中裂逐,查看GNU的實(shí)現(xiàn)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
-(BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)date
{
? NSAutoreleasePool * arp = [NSAutoreleasePool new];
? NSString * savedMode = _currentMode;
? GSRunLoopCtxt *上下文歹鱼;
? NSDate * d;
? NSAssert(mode!= nil卜高,NSInvalidArgumentException);
? / *處理所有待處理的通知弥姻。
? * /
? GSPrivateNotifyASAP(mode);
? / *并處理循環(huán)中安排的所有執(zhí)行者(例如,來(lái)自
? 另一個(gè)線程的東西掺涛。
? * /
? _currentMode =模式庭敦;
? context = NSMapGet(_contextMap,mode);
? [self _checkPerformers:context];
? _currentMode = savedMode;
? / *找出我們可以在第一個(gè)限制日期之前等待多長(zhǎng)時(shí)間薪缆。
? *如果沒(méi)有輸入源或計(jì)時(shí)器秧廉,請(qǐng)立即返回。
? * /
? d = [self limitDateForMode:模式];
? if(nil == d)
? ? {
? ? ? [樹(shù)樁排水]拣帽;
? ? ? 返回否疼电;
? ? }
? / *使用我們擁有的兩個(gè)日期中的較早日期(零日期就像遙遠(yuǎn)的過(guò)去)。
? * /
? if(nil == date)
? ? {
? ? ? [self acceptInputForMode:模式beforeDate:nil];
? ? }
? else
? ? {
? ? ? / *保留日期诞外,以防計(jì)時(shí)器(或其他事件)觸發(fā)
? ? ? *。
? ? ? * /
? ? ? d = [[d較早的日期:日期]副本];
? ? ? [self acceptInputForMode:模式beforeDate:d];
? ? ? 發(fā)布(d)灾票;
? ? }
? [排泄口]峡谊;
? 返回是;
}
在GNU的實(shí)現(xiàn)中刊苍,targer執(zhí)行相應(yīng)的動(dòng)作操作是在[self acceptInputForMode: mode beforeDate: d];中既们,可以看到在runMode: (NSString*)mode beforeDate: (NSDate*)date方法中,其實(shí)是包裹了一個(gè)autoreleasepool的正什,也就是arp啥纸,如果在深入一些函數(shù)里面,發(fā)現(xiàn)實(shí)際上很多地方都有autoreleasepool的函數(shù)婴氮,因此即使是我們自定義的源斯棒,執(zhí)行函數(shù)中沒(méi)有釋放autoreleasepool的操作也不用擔(dān)心,系統(tǒng)在各個(gè)關(guān)鍵入口都給我們加了這些操作主经。
文章到此就告一段落了荣暮,還有一種端口源,也就是source1罩驻,這種source我沒(méi)有去看穗酥,好奇的同學(xué)可以去看一看如果有什么不對(duì)我們一起討論。
1.
在runloop的運(yùn)行中:beforeDate,以及一些源的回調(diào)中砾跃,有autoreleasepool的push和pop操作骏啰,總結(jié)就是系統(tǒng)在1.子線程在使用autorelease對(duì)象時(shí),如果沒(méi)有autoreleasepool會(huì)在autoreleaseNoPage中懶加載一個(gè)出來(lái)抽高。很多地方都差不多自動(dòng)釋放的管理操作判耕。
3.就算插入沒(méi)有彈出也沒(méi)關(guān)系,在線程退出的時(shí)候會(huì)釋放資源厨内,執(zhí)行AutoreleasePoolPage :: tls_dealloc祈秕,在這里面會(huì)清空autoreleasepool。
##參考