在iOS開發中,循環引用是個老生常談的問題.delegate爲啥使用weak修飾,block爲何須要weakSelf或strongSelf?經過閱讀他人的文章並結合本身理解來闡述一下本身對循環引用的理解,如有不足但願你們指出.程序員
首先須要先了解內存中的分區:棧區、堆區、靜態區(全局區).具體職責劃分以下:bash
如圖所示: 多線程
能夠看見在上面的說明中,只有堆區是須要程序員管理的,而循環引用也與此有關,因此咱們通常只須要關注堆區內存就能夠了,即循環引用致使堆中的內存沒法正常回收.那麼內存的回收又和咱們iOS的回收機制有關,也就是你們都知道的引用計數:app
"對象"就至關於上圖中的燈,而"持有對象"就至關於圖中的人.第一個進來打開燈的人至關於進行了一個alloc操做,建立了對象這塊內存,並使引用計數變爲1.以後進來的人就至關於持有這個對象,使引用計數+1(retain),離開的人就使該對象引用計數-1(release).只要辦公室裏面還有人在(還有人持有這個對象),這個"燈"就不會關(dealloc).最後一我的走了,那麼燈就關了,這塊內存也就被成功釋放了. 過程如圖所示 函數
正常的內存釋放過程是這樣的,B對象是A對象的一個屬性,也就是A持有B,如今要釋放掉A了,給A發一個release消息,這個時候A的引用計數變爲0,就要走dealloc方法,在dealloc方法裏面會給A持有的全部對象發送一條release消息,固然包括B,也就是[B release].而後B的引用計數也變爲0,執行dealloc.這樣A和B就都釋放掉了,沒有形成任何內存問題,內存正確回收. atom
那麼何時會形成循環引用呢,顧名思義,就是互相持有,造成一個閉環,致使誰也沒法正確釋放.如圖所示: spa
形成循環引用的過程是這樣的:想要讓A釋放,須要B給A發送release消息,由於此時B持有A,但B只有在dealloc的時候會發送release消息,要讓B執行dealloc方法,就須要A發送release消息給B,要讓A發送release消息給B就須要A執行dealloc方法,要讓A執行dealloc方法又須要B給A發送release消息...這樣循環往復,都在等對方給本身發送release消息,形成誰也沒法dealloc,內存也就沒法釋放.就像兩我的都拽着對方的手說你先鬆我才鬆同樣,誰都不願先鬆,而後就這樣一直拽着對方直到天荒地老. 這種感受就像下面這張圖同樣: 線程
//ClassA:
@protocol ClssADelegate <NSObject>
- (void)doNothing;
@end
@interface ClassA : UIViewController
@property (nonatomic, strong) id <ClssADelegate> delegate;
@end
//ClassB:
@interface ClassB ()<ClassADelegate>
@property (nonatomic, strong) ClassA *classA;
@end
@implementation ClassB
- (void)viewDidLoad {
[super viewDidLoad];
self.classA = [[ClassA alloc] init];
self.classA.delegate = self;
}
複製代碼
在上面的代碼中,classB持有classA,而classA中delegate屬性使用strong強引並指向了self(classB),因此classA經過delegate持有了classB.這樣就形成了循環引用.你們可能都知道該如何解決這個問題,那就是使用weak替代strong.這也是爲何delegate一般都用weak修飾的緣由.這裏順便簡單說一下weak吧. weak是弱引用,用weak描述修飾或者所引用對象的計數器不會加一,而且會在引用的對象被釋放的時候自動被設置爲nil,大大避免了野指針訪問壞內存引發崩潰的狀況,另外weak還能夠用於解決循環引用.3d
@interface ClassA ()
@property (nonatomic, copy) Block block;
@property (nonatomic, assign) NSInteger num;
@end
@implementation ClassA
- (void)viewDidLoad {
[super viewDidLoad];
self.block = ^{
self.num = 1;
};
}
複製代碼
在上面的代碼中的block是存在於堆內存中,classA持有block,而堆內存中的block中又持有了self,這樣就形成了循環引用.若是是棧中的block就不會形成這種問題,以下所示:指針
void (^block)(void) = ^{
self.num = 1;
};
block();
複製代碼
要解決Block形成的這種循環引用,經常使用的解決方式是使用WeakSelf,以下:
@interface ClassA ()
@property (nonatomic, copy) Block block;
@property (nonatomic, assign) NSInteger num;
@end
@implementation ClassA
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self
self.block = ^{
weakSelf.num = 1;
};
}
複製代碼
在上面的兩個例子中能夠看出,使用weak弱引用替代strong強引用來讓環消失是很是有效的方式,在大多數狀況下用這種方法就能夠了,但在某些狀況下仍是有缺陷的
有一種場景就是在block執行過程,self被釋放掉了,這個時候若是去訪問self的話就會發生錯誤.代碼以下:
#import "ControllerB.h"
@interface ControllerB ()
@property (nonatomic,copy) void (^block)(void);
@property (nonatomic, strong) NSString *str;
@end
@implementation ControllerB
- (void)viewDidLoad {
[super viewDidLoad];
self.str = @"test";
__weak typeof(self) weakSelf = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", weakSelf.str);
});
};
self.block();
}
複製代碼
那麼這個時候就須要在block強引用self,直到block執行再釋放掉self.代碼以下:
#import "ControllerB.h"
@interface ControllerB ()
@property (nonatomic,copy) void (^block)(void);
@property (nonatomic, strong) NSString *str;
@end
@implementation ControllerB
- (void)viewDidLoad {
[super viewDidLoad];
self.str = @"test";
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", strongSelf.str);
});
};
self.block();
}
複製代碼
strongSelf是個局部變量,存在於棧中,而棧中內存系統會自動回收,也就是在block執行結束後回收,不會形成循環引用.同時strongSelf使ControllerB的引用計數加1,致其在pop後不會立馬執行dealloc銷燬str屬性,由於此時strongSelf持有了ControllerB,4秒事後,block執行並打印str,局部變量strongSelf被系統回收,其持有的ControllerB也會執行dealloc方法.
以前用RAC的時候看見裏面的宏定義@weakify和@strongify,以爲很是高明.這樣的話不只很方便,並且防止不當心在block中使用self形成的循環引用. 那麼上面的ControllerB的代碼就能夠改爲這樣:
#import "ControllerB.h"
@interface ControllerB ()
@property (nonatomic,copy) void (^block)(void);
@property (nonatomic, strong) NSString *str;
@end
@implementation ControllerB
- (void)viewDidLoad {
[super viewDidLoad];
self.str = @"test";
@weakify(self)
self.block = ^{
@strongify(self)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", self.str);
});
};
self.block();
}
複製代碼
這樣就能夠隨意的在block中使用self了