今天 抽空看了下 *Objective-C高階程式設計iOS與OSX多執行緒和記憶體管理*,發現自己之前所理解的
為什麼block會發生迴圈引用?`有些理解是錯誤的,還好看了這個書,最後弄清楚了,希望寫出來,既能算是一種總結,又能讓其他小夥伴避免再遇到這個坑!下面 讓我們一起來看幾個場景!
專案簡單的類結構
import "ViewController.h"
#import "DetailViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
DetailViewController *detailVC = [DetailViewController new];
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
NSLog(@"d: %@--", d);
};
[self presentViewController:d animated:YES completion:nil];
}
//---------------------DetailViewController-----------------------------
@interface DetailViewController : UIViewController
@property (nonatomic, copy) void(^testMemoryLeaksBLock)(DetailViewController *detailVC);
@end
@implementation DetailViewController
-(void)dealloc {
NSLog(@"dealloc");
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
!_testMemoryLeaksBLock ?: _testMemoryLeaksBLock(self);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self dismissViewControllerAnimated:YES completion:nil];
}
複製程式碼
場景一
正如專案結構那樣,當detailVC disappear時候,回撥block是否會有記憶體洩漏呢?
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
NSLog(@"d: %@--", d);
};
複製程式碼
sow 正如上面分析圖,一般情況下,是有記憶體洩漏的,但就是由於detailVC.testMemoryLeaksBLock
所持有的d
指標是一個區域性變數,當block執行完之後,這個區域性變數引用計數就為0,就被釋放了,因此d
就不再持有detailVC
,detailVC.testMemoryLeaksBLock
對detailVC
就不再持有, 迴圈引用被打破,還是會走 -[DetailViewController dealloc]
的.
區域性變數
和 weak弱指標
,只會強引用 block 外部strong指標
,並不是 block結束之後就會釋放掉區域性變數,所以不會引起迴圈
,因為如果像那樣說的話,假如block不執行,那區域性變數豈不是就釋放不掉了。
具體看一下例1:
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
__weak typeof(student) weakStu = student;
Student *strongStu = weakStu;
student.study = ^{
//第1種寫法
//Student *strongStu = weakStu;
//第2種寫法
//__strong typeof(weakStu) strongStu = weakStu;
//第3種寫法
//typeof(student) strongStu = weakStu;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"my name is = %@", strongStu.name);
});
};
}
複製程式碼
例1是有記憶體洩漏的沒有走-[Student dealloc]
,因為未執行student.study
, 所以dispatch_after block
也不會走,但是dispatch_after bLock
卻強引用了strongStu
還是發生了迴圈引用。這個比較好理解。但是下面例2,就改了一行程式碼 我怎麼也想不通為什麼 沒有發生迴圈引用,走了-[Student dealloc]
.
例2:
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
__weak typeof(student) weakStu = student;
student.study = ^{
/**
* 三種寫法是一樣的效果,都是為了防止區域性變數`student`在`viewDidLoad `之後銷燬。如果不這樣寫的話,
* 由於`student`區域性變數被銷燬,所以為nil,再走到`dispatch_after block`時候,由於weakStu是弱指標,
* 所以不會強引用,最後列印為null,這不是我們想要的效果,`AFNetWorking`好多第三方庫也是這麼寫的
* 第1種寫法
* Student *strongStu = weakStu;
* 第2種寫法
* __strong typeof(weakStu) strongStu = weakStu;
* 第3種寫法
* typeof(student) strongStu = weakStu;
*/
//隨便取哪一種寫法,這裡取第一種寫法
Student *strongStu = weakStu;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"my name is = %@", strongStu.name);
});
};
}
複製程式碼
dispatch_after block
強引用了外部變數strongStu ,
這種不呼叫student.study()
的寫法為什麼沒有迴圈引用呢,但是如果在ViwDidLoad
結尾呼叫student.study()
,那麼會在2秒後執行完dispatch_after block
才會走-[Student dealloc]
,不就說明dispatch_after block
持有這個student
,走完才回銷燬,那如果不執行student.study()
的話,按道理講,應該也會被dispatch_after block
持有這個student
,為什麼 不會產生迴圈引用呢。
匪夷所思如果有哪個大神路過,麻煩給個思路,我真想不明白。
場景二
block程式碼改成下面這樣,當detailVC disappear時候,回撥block是否又會有記憶體洩漏呢?
DetailViewController *detailVC = [DetailViewController new];
id tmp = detailVC;
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
NSLog(@"d: %@--\nd: %@", d, tmp);
};
複製程式碼
答案:的確有記憶體洩漏 ,因為迴圈引用的問題,具體看下圖:
上面情況如果懂的話,下面這種寫法是一樣的,經典的迴圈引用
detailVC
持有testMemoryLeaksBLock
,testMemoryLeaksBLock
持有 detailVC
,導致兩個引用計數都無法減一,最後誰也釋放不了誰!!
DetailViewController * detailVC = [DetailViewController new];
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
NSLog(@"detailVC: %@", detailVC);
};
複製程式碼
##如何解決記憶體洩漏 原因我們瞭解了,現在是該如何解決了,下面根據之前場景逐一給出不同的思路,注意思路很重要,因為有很多種解決思路,都要搞清楚,舉一反三,因為場景一沒有記憶體洩漏,因此主要針對場景二 ###針對場景二的解決方案1: 有問題的程式碼:
DetailViewController *detailVC = [DetailViewController new];
id tmp = detailVC;
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
NSLog(@"tmp: %@", tmp);
};
複製程式碼
方案一的程式碼:
DetailViewController * detailVC = [DetailViewController new];
__block id tmp = detailVC;
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
NSLog(@"tmp: %@", tmp);
tmp = nil;
};
複製程式碼
雖然解決了記憶體洩漏,但是細心的看客姥爺肯定發現了這樣寫的一個弊端,沒錯,那就是 如果 detailVC.testMemoryLeaksBLock ()
沒有呼叫的話,還是會造成記憶體洩漏的,因為testMemoryLeaksBLock
還是間接地強引用了detailVC
, 算是一個思路吧,畢竟思路要廣才能 想的更多,學的更多,不是嗎!
###針對場景二的解決方案2: 有問題的程式碼:
DetailViewController *detailVC = [DetailViewController new];
id tmp = detailVC;
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
NSLog(@"tmp: %@", tmp);
};
複製程式碼
方案二的程式碼:
DetailViewController * detailVC = [DetailViewController new];
__weak id weakTmp = detailVC;
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
NSLog(@"weakTmp: %@", weakTmp);
};
複製程式碼
這也是平常開發中用得最多的一種解決迴圈引用的方法。來給小總結吧:方案一和方案二都是斷掉testMemoryLeaksBLock
對detailVC
的強引用,自然可以;其實開發中還有一種方案也是可以的,那就是 斷掉detailVC
對 testMemoryLeaksBLock
的強引用。
###針對場景二的解決方案3: 有問題的程式碼:
DetailViewController *detailVC = [DetailViewController new];
id tmp = detailVC;
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
NSLog(@"tmp: %@", tmp);
};
複製程式碼
方案三的程式碼:
@implementation DetailViewController
-(void)dealloc {
NSLog(@"dealloc");
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
!_ testMemoryLeaksBLock ?: _testMemoryLeaksBLock(self);
//就加了下面一行程式碼也是可以的,因為一旦手動把 _testMemoryLeaksBLock置為空, 那麼這個block就沒有任何物件持有它,
//換一句話說就是沒有物件強引用這個block, 那麼如果這個block之前在堆裡,它就會被廢棄掉,
_testMemoryLeaksBLock= nil;
}
@end
複製程式碼
每一次執行完block之後都手動置nil,斷掉detailVC
對 testMemoryLeaksBLock
的強引用也不失為一種方法。
####更正:block不會強引用 block內部的區域性變數和 弱指標,只會強引用 block 外部strong指標,並不是 block結束之後就會釋放掉區域性變數,所以不會引起迴圈
,因為如果像那樣說的話,假如block不執行,那區域性變數豈不是就釋放不掉了。
####總結:這也是平常開發中三種解決迴圈引用的方法。希望大家週末都玩得開心,麼麼噠,下週準備寫 GCD,有好多 執行緒同步的 知識,望 共同努力,共勉,手動!