今天 抽空看了下 *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]
的.
更正:block不會強引用 block內部的局部變量
和 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
的強引用也不失為一種方法。