一、BLOCK 循環引用
一般表現為,某個類将block作為自己的屬性變量,然後該類在block的方法體裡面又使用了該類本身。構成循環引用。
// 定義 block 的時候,會對外部變量做一次 copy,強引用, self自身為強引用。
解決方案如下:
1 #import "ViewController.h"
2 #import "NetworkTools.h"
3
4 @interface ViewController ()
5 @property (nonatomic, strong) NetworkTools *tools;
6 @end
7
8 @implementation ViewController
9 // 1. 解除循環引用,需要注意打斷引用鍊條即可!
10 - (void)viewDidLoad {
11 [super viewDidLoad];
12
13 // 局部變量不會産生循環應用,全局屬性會産生循環引用
14 self.tools = [[NetworkTools alloc] init];
15
16 // 1. 定義 block 的時候,會對外部變量做一次 copy,會對 self 進行強引用
17
18 // 解除循環引用方法1
19 // __weak 是 iOS 5.0 推出的
20 // 如果異步操作沒有完成,釋放控制器,__weak 本身是弱引用
21 // 當異步執行完畢,進行回調,self 已經被釋放,無法通路屬性,也無法調用方法
22 // __weak 相當于 weak,不會做強引用,但是如果對象被釋放,執行的位址,會指向 nil
23 // __weak 更安全
24 __weak typeof(self) weakSelf = self;
25
26 // 解除循環引用方法2
27 // __unsafe_unretained 是 iOS 4.0 推出的
28 // MRC 經典錯誤,EXC_BAD_ACCESS 壞記憶體通路,野指針
29 // 相當于 assign,不會做強引用,但是如果對象被釋放,記憶體位址保持不變,如果此時再調用,就會出現野指針通路
30 // __unsafe_unretained typeof(self) weakSelf = self;
31
32 [self.tools loadData:^(NSString *html) {
33 // strongSelf 強引用,對 weakSelf 進行強引用,本意,希望在異步完成後,繼續執行回調代碼
34 //然而并沒有什麼作用!!!!!!!!
35 __strong typeof(self) strongSelf = weakSelf;
36
37 NSLog(@"%@ %@", html, strongSelf.view);
38 }];
39 }
40
41 - (void)dealloc {
42 NSLog(@"控制器 88");
43 }
44
45 @end
二、計時器NSTimer循環引用
主要是因為從timer的角度,timer認為調用方self被析構時會進入dealloc,在dealloc可以順便将timer的計時停掉并且釋放記憶體;但是從self的角度,他認為timer不停止計時不析構,那我永遠沒機會進入dealloc。循環引用,互相等待,子子孫孫無窮盡也。
例子說明:
一方面,NSTimer經常會被作為某個類的成員變量,而NSTimer初始化時要指定self為target,容易造成循環引用。 另一方面,若timer一直處于validate的狀态,則其引用計數将始終大于0。先看一段NSTimer使用的例子(ARC模式):
1 import <Foundation/Foundation.h>
2
3 interface Friend : NSObject
4 -(void)cleanTimer;
5 end
6
7 import "Friend.h"
8
9 interface Friend ()
10 STimer *_timer;
11 end
12
13 implementation Friend
14
15 -(id)init
16 {
17 if (self = [super init]) {
18 _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimer:)
19 userInfo:nil repeats:YES];
20 }
21 return self;
22 }
23
24 - (void)handleTimer:(id)sender
25 {
26 NSLog(@"%@ say: Hi!", [self class]);
27 }
28
29 - (void)cleanTimer
30 {
31 [_timer invalidate];
32 _timer = nil;
33 }
34
35 - (void)dealloc
36 {
37 [self cleanTimer];
38 NSLog(@"[Friend class] is dealloced");
39 }
40
41 @end
在類外部初始化一個Friend對象,并延遲5秒後将friend釋放(外部運作在非arc環境下)
1 Friend *f = [[Friend alloc] init];
2 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
3 [f release];
4 });
我們所期待的結果是,初始化5秒後,f對象被release,f的dealloc方法被調用,在dealloc裡面timer失效,對象被析構。但結果卻是如此:
1 2015-03-18 18:00:35.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
2 2015-03-18 18:00:36.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
3 2015-03-18 18:00:37.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
4 2015-03-18 18:00:38.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
5 2015-03-18 18:00:39.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!//運作了5次後沒按照預想的停下來
6 2015-03-18 18:00:40.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
7 2015-03-18 18:00:41.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
8 2015-03-18 18:00:42.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
9 2015-03-18 18:00:43.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
10 2015-03-18 18:00:44.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!<br>.......根本停不下來.....
這是為什麼呢?主要是因為從timer的角度,timer認為調用方(Friend對象)被析構時會進入dealloc,在dealloc可以順便将timer的計時停掉并且釋放記憶體;但是從Friend的角度,他認為timer不停止計時不析構,那我永遠沒機會進入dealloc。循環引用,互相等待,子子孫孫無窮盡也。問題的症結在于-(void)cleanTimer函數的調用時機不對,顯然不能想當然地放在調用者的dealloc中。一個比較好的解決方法是開放這個函數,讓Friend的調用者顯式地調用來清理現場。如下:
1 Friend *f = [[Friend alloc] init];
2 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
3 [f cleanTimer];
4 [f release];
5 });
三、委托delegate
在委托問題上出現循環引用問題已經是老生常談了,本文也不再細講,規避該問題的殺手锏也是簡單到哭,一字訣:聲明delegate時請用assign(MRC)或者weak(ARC),千萬别手賤玩一下retain或者strong,畢竟這基本逃不掉循環引用了!
上面說的是我們常見的,其實循環引用就是說我們的強引用形成了閉環,還會有很多自己寫的代碼中會出現,平時還是要注意寫法。
不好意思,下面再啰嗦一遍,進一步說明:
循環引用,指的是多個對象互相引用時,使得引用形成一個環形,導緻外部無法真正是否掉這塊環形記憶體。其實有點類似死鎖。
舉個例子:A->B->C->....->X->B ->表示強引用,這樣的B的引用計數就是2,假如A被系統釋放了,理論上A會自動減小A所引用的資源,就是B,那麼這時候B的引用計數就變成了1,所有B無法被釋放,然而A已經被釋放了,所有B的記憶體部分就肯定無法再釋放再重新利用這部分記憶體空間了,導緻記憶體洩漏。
情況一:delegate
Delegate是ios中開發中最常遇到的循環引用,一般在聲明delegate的時候都要使用弱引用weak或者assign
@property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;
當然怎麼選擇使用assign還是weak,MRC的話隻能用assign,在ARC的情況下最好使用weak,因為weak修飾的變量在是否後自動為指向nil,防止不安全的野指針存在
情況二:Block
Block也是比較常見的循環引用問題,在Block中使用了self容易出現循環引用,是以很多人在使用block的時候,加入裡面有用到self的操作都會聲明一個__weak來修飾self。其實便不是這樣的,不是所有使用了Block都會出現Self循環引用問題,隻有self擁有Block的強引用才會出現這種情況。
是以一般在函數中臨時使用Block是不會出現循環應用的,因為這時候Block引用是屬于棧的。當棧上的block釋放後,block中對self的引用計數也會減掉
當然不一定要Self對Block有直接的引用才會出現,假如self的變量B,B中有個Block變量,就容易出現這種情況,好的是在block出現循環引用的,xcode7會出現警告提示(之前版本不确定)。
情況三:NSTimer
這是一個神奇的NSTimer,當你建立使用NSTimer的時候,NSTimer會預設對目前self有個強引用,所有在self使用完成打算是否的時候,一定要先使用NSTimer的invalidate來停止是否時間控制對self的引用
[_timer invalidate];
轉載于:https://www.cnblogs.com/xujinzhong/p/8427572.html