循環(huán)引用的原因
眾所周知,ARC下用block會產(chǎn)生循環(huán)引用的問題,造成泄露的原因是啥呢?
最簡單的例子,如下面代碼:
| 123 | [self.teacher requestData:^(NSData *data) { self.name = @"case";}]; |
此種情況是最常見的循環(huán)引用導致的內(nèi)存泄露了,在這里,self強引用了teacher, teacher又強引用了一個block,而該block在回調(diào)時又調(diào)用了self,會導致該block又強引用了self,造成了一個保留環(huán),最終導致self無法釋放。
self -> teacher -> block -> self
一般性的解決方案
| 12345 | __weak typeof(self) weakSelf = self; [self.teacher requestData:^(NSData *data) { typeof(weakSelf) strongSelf = weakSelf; strongSelf.name = @"case"; }]; |
通過__weak的修飾,先把self弱引用(默認是強引用,實際上self是有個隱藏的__strong修飾的),然后在block回調(diào)里用weakSelf,這樣就會打破保留環(huán),從而避免了循環(huán)引用,如下:
self -> teacher -> block -> weakSelf
PS:一般會在block回調(diào)里再強引用一下weakSelf(typeof(weakSelf) strongSelf = weakSelf;),因為__weak修飾的都是存在棧內(nèi),可能隨時會被系統(tǒng)釋放,造成后面調(diào)用weakSelf時weakSelf可能已經(jīng)是nil了,后面用weakSelf調(diào)用任何代碼都是無效的。
通過demo證明哪些情況下有泄漏
雖然說用block時會產(chǎn)生循環(huán)引用,但并不是所有情況下都會有內(nèi)存泄露的問題,看個demo。
先發(fā)demo地址:https://github.com/yuedong56/BlockRetainCycleDemo
進入demo,有個Button,點擊Button push到SecondViewController,
SecondViewController中有六種情況的Button,每點一個Button會觸發(fā)一個block,
點擊返回,回到首頁,如果執(zhí)行了dealloc,證明SecondViewController正常釋放,否則,證明內(nèi)存泄露了。

下面只貼demo里的關(guān)鍵代碼了,全的代碼請自行下載demo,大家看下面的六種情況,是否會產(chǎn)生內(nèi)存泄漏。
| 1234567 | //情況一- (void)case1 { NSLog(@"case 1 Click"); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ self.name = @"case 1"; });} |
| 123456789 | //情況二- (void)case2 { NSLog(@"case 2 Click"); __weak typeof(self) weakSelf = self; [self.teacher requestData:^(NSData *data) { typeof(weakSelf) strongSelf = weakSelf; strongSelf.name = @"case 2"; }];} |
| 1234567 | //情況三- (void)case3 { NSLog(@"case 3 Click"); [self.teacher requestData:^(NSData *data) { self.name = @"case 3"; }];} |
| 12345678 | //情況四- (void)case4 { NSLog(@"case 4 Click"); [self.teacher requestData:^(NSData *data) { self.name = @"case 4"; self.teacher = nil; }];} |
| 12345678 | //情況五- (void)case5 { NSLog(@"case 5 Click"); Teacher *t = [[Teacher alloc] init]; [t requestData:^(NSData *data) { self.name = @"case 5"; }];} |
| 1234567891011 | //情況六- (void)case6 { NSLog(@"case 6 Click"); [self.teacher callCase6BlackEvent]; self.teacher.case6Block = ^(NSData *data) { self.name = @"case 6"; //下面兩句代碼任選其一 self.teacher = nil;// self.teacher.case6Block = nil; };} |
分析
情況一:執(zhí)行了dealloc,不泄露,此情況雖然是block,但未形成保留環(huán)block -> self
情況二:執(zhí)行了dealloc,不泄露,此情況就是內(nèi)存泄漏后的一般處理了 self ->teacher ->block ->strongSelf,后面那個strongSelf和原來的self并沒有直接關(guān)系,因為strongSelf是通過weakSelf得來的,而weakSelf又沒有強引用原來的self
情況三:未執(zhí)行dealloc,內(nèi)存泄漏,此情況就是最典型的循環(huán)引用了,形成保留環(huán)無法釋放,self ->teacher ->block ->self
情況四:執(zhí)行了dealloc,不泄露,雖然也是保留環(huán),但通過最后一句,使self不再強引用teacher,打破了保留環(huán)
情況五:執(zhí)行了dealloc,不泄露,未形成保留環(huán) t ->block ->self
情況六:執(zhí)行了dealloc,不泄露,最后兩句代碼任選其一即可防止內(nèi)存泄漏,self.teacher 或者 case6Block 置為空都可以打破 retain cycle
PS: 雖然情況四、情況六的寫法都可以防止內(nèi)存泄漏,不過為了統(tǒng)一,個人建議最好還是按照普通寫法即情況二的寫法。
新聞熱點
疑難解答