不知道從什么時(shí)候開始张咳,我的腦子里就有一個(gè)印象:“在block中用self容易造成retain cycle捕捂,雖然有時(shí)候編譯器會(huì)警告但也不能保證編譯器每次都發(fā)現(xiàn)retain cycle,所以保險(xiǎn)起見還是每次都用weakSelf好了( ̄▽ ̄)”
果然這種無腦的想法會(huì)出問題(╥﹏╥)耿币。
ps.這篇好水效床,因?yàn)樵硎裁吹臅锟戳耍€沒理解透徹到可以寫出來>.<
崩潰
因?yàn)樽罱谧龅捻?xiàng)目中侯养,還有一些很久以前留下來的,用MRC內(nèi)存管理方式的代碼澄干。我給其中一個(gè)文件加上了類似這樣的兩個(gè)方法:
//MRC
- (void)alarmLater {
__block typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf alarm];
});
}
- (void)alarm {
NSLog(@"Hello World!");
}
因?yàn)榱?xí)慣了無腦用weakSelf逛揩。MRC中,__block
修飾符可以避免block對(duì)self對(duì)象的retain麸俘,所以我就這么寫了辩稽。
然而運(yùn)行的結(jié)果是,崩潰(╥﹏╥)
* thread #1: tid = 0x22796c, 0x00000001060de80b libobjc.A.dylib`objc_msgSend + 11, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
frame #0: 0x00000001060de80b libobjc.A.dylib`objc_msgSend + 11
* frame #1: 0x0000000105bcaaee XSQBlockCrashDemo`__27-[XSQLaterAlarm alarmLater]_block_invoke(.block_descriptor=<unavailable>) + 46 at XSQLaterAlarm.m:17
frame #2: 0x0000000108d36e5d libdispatch.dylib`_dispatch_call_block_and_release + 12
frame #3: 0x0000000108d5749b libdispatch.dylib`_dispatch_client_callout + 8
frame #4: 0x0000000108d3c746 libdispatch.dylib`_dispatch_after_timer_callback + 334
frame #5: 0x0000000108d5749b libdispatch.dylib`_dispatch_client_callout + 8
frame #6: 0x0000000108d4a8a5 libdispatch.dylib`_dispatch_source_latch_and_call + 1750
frame #7: 0x0000000108d45830 libdispatch.dylib`_dispatch_source_invoke + 1057
frame #8: 0x0000000108d3f111 libdispatch.dylib`_dispatch_main_queue_callback_4CF + 1324
frame #9: 0x00000001065afd09 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
frame #10: 0x00000001065712c9 CoreFoundation`__CFRunLoopRun + 2073
frame #11: 0x0000000106570828 CoreFoundation`CFRunLoopRunSpecific + 488
frame #12: 0x0000000109e09ad2 GraphicsServices`GSEventRunModal + 161
frame #13: 0x00000001069fc610 UIKit`UIApplicationMain + 171
frame #14: 0x0000000105bca94f XSQBlockCrashDemo`main(argc=1, argv=0x00007fff5a035650) + 111 at main.m:14
frame #15: 0x0000000108d8b92d libdyld.dylib`start + 1
這是因?yàn)榇用模?dāng)block中調(diào)用到[weakSelf alarm]
的時(shí)候搂誉,self已經(jīng)被回收,這句話相當(dāng)于訪問了一個(gè)懸垂指針,導(dǎo)致崩潰炭懊。
為什么需要weakSelf并级?
有很多文檔啊博客啊都解釋過這個(gè)問題,簡(jiǎn)單的說:
- block中使用的賦值給附有
__strong
修飾符的自動(dòng)變量的對(duì)象由于被堆上的block所持有侮腹,因而可超出其變量作用域而存在嘲碧。
- 在Grand Central Dispatch的API中傳遞block時(shí),棧上的block會(huì)復(fù)制到堆父阻。
如果一個(gè)對(duì)象直接或間接持有了block愈涩,而block又持有那個(gè)對(duì)象,則會(huì)產(chǎn)生一個(gè)retain cycle加矛,導(dǎo)致內(nèi)存泄漏履婉。所以在MRC下,可以用__block
修飾符修飾block中使用的變量斟览,而在ARC下毁腿,可以用__weak
或__unsafe_unretained
修飾符修飾block中使用的變量,來破壞這個(gè)retain cycle苛茂。
由于很多情況下已烤,造成retain cycle的對(duì)象都是self,所以這些情況下妓羊,會(huì)使用weakSelf來破壞retain cycle胯究。
然而重點(diǎn)是“retain cycle”,而不是“weakSelf”躁绸。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf alarm];
});
這段代碼中裕循,self并沒有持有block,所以當(dāng)block需要訪問self的時(shí)候净刮,使用weakSelf是多余的剥哑。
所以在這段代碼中,直接在block中使用self庭瑰,不會(huì)產(chǎn)生retain cycle,也可以讓self對(duì)象一直存活到block被運(yùn)行的時(shí)候抢埋。
- (void)alarmLater {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self alarm];
});
}
扯點(diǎn)教訓(xùn)
因?yàn)橛龅搅诉@個(gè)bug弹灭,最近也補(bǔ)了點(diǎn)關(guān)于block的知識(shí)【韭ⅲ看了《Objective-C高級(jí)編程》里關(guān)于block實(shí)現(xiàn)的那段穷吮,覺得對(duì)block的認(rèn)識(shí)清晰了一些。以前一直看不懂里面成片的C代碼饥努,這次居然明白了個(gè)大概捡鱼,不知道是不是我進(jìn)步了一點(diǎn)。
無腦總是不對(duì)的酷愧。作為一個(gè)程序員驾诈,寫下的每一行代碼缠诅,都得清楚的知道是為什么吧。
參考
《Objective-C高級(jí)編程》
談Objective-C Block的實(shí)現(xiàn)
正確使用Block避免Cycle Retain和Crash