今天 抽空看了下 *Objective-C高級編程iOS與OSX多線程和內存管理*,發現本身以前所理解的
爲何block會發生循環引用?`有些理解是錯誤的,還好看了這個書,最後弄清楚了,但願寫出來,既能算是一種總結,又能讓其餘小夥伴避免再遇到這個坑!下面 讓咱們一塊兒來看幾個場景!編程
項目簡單的類結構bash
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 正如上面分析圖,通常狀況下,是有內存泄漏的,但就是因爲appdetailVC.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]
.ui
例2:atom
- (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
,爲何 不會產生循環引用呢。spa
匪夷所思若是有哪一個大神路過,麻煩給個思路,我真想不明白。線程
block代碼改爲下面這樣,當detailVC disappear時候,回調block是否又會有內存泄漏呢?3d
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,有好多 線程同步的 知識,望 共同努力,共勉,手動!