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

下面只貼demo里的關鍵代碼了,全的代碼請自行下載demo,大家看下面的六種情況,是否會產生內存泄漏。
| 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; };} |
分析
情況一:執行了dealloc,不泄露,此情況雖然是block,但未形成保留環block -> self
情況二:執行了dealloc,不泄露,此情況就是內存泄漏后的一般處理了 self ->teacher ->block ->strongSelf,后面那個strongSelf和原來的self并沒有直接關系,因為strongSelf是通過weakSelf得來的,而weakSelf又沒有強引用原來的self
情況三:未執行dealloc,內存泄漏,此情況就是最典型的循環引用了,形成保留環無法釋放,self ->teacher ->block ->self
情況四:執行了dealloc,不泄露,雖然也是保留環,但通過最后一句,使self不再強引用teacher,打破了保留環
情況五:執行了dealloc,不泄露,未形成保留環 t ->block ->self
情況六:執行了dealloc,不泄露,最后兩句代碼任選其一即可防止內存泄漏,self.teacher 或者 case6Block 置為空都可以打破 retain cycle
PS: 雖然情況四、情況六的寫法都可以防止內存泄漏,不過為了統一,個人建議最好還是按照普通寫法即情況二的寫法。
新聞熱點
疑難解答