iOS之循環引用問題
- 循環引用的產生
- 內存分佈區域
- 內存管理機制
- 循環引用常見問題
循環引用的產生
內存中和變量有關的分區:堆、棧、靜態區。其中,棧和靜態區是操作系統自己管理的,對程序員來說相對透明,所以,一般我們只需要關注堆的內存分配,而循環引用的產生,也和其息息相關,即循環引用會導致堆裏的內存無法正常回收,從而導致內存泄漏。
內存分佈區域
1、棧區(stack)— 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。分配內存的時候,棧區的地址是 從上往下分配(越分配地址越小 i 地址比j大)
2、堆區(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。堆區的地址是 由下往上的
3、全局區(靜態區)(static)— 全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。 - 程序結束後有系統釋放
4、文字常量區— 常量字符串就是放在這裏的。 程序結束後由系統釋放
5、程序代碼區— 存放函數體的二進制代碼。
內存管理模型
在iOS中有2套內存管理機制:MRC(MannulReference Counting)和ARC(Automatic Reference Counting)。其中ARC起源於iOS 4.3,在那之前,蘋果開發者只能手動使用retain和release來進行內存管理。這樣做的問題很明顯,如果你在開發過程中,一個不小心沒有retain和release成對出現,那麼很容易使得內存沒有釋放,最後導致程序內存不足而導致閃退。
MRC
在你創建一個對象的時候,他會需要進行retain,然後在你不在持有他的時候進行release,所以每個對象都有一個retain count來進行計數。
內存管理規則如下:
1> 當調用alloc、new、copy(mutableCopy)方法產生一個新對象的時候,就必須在最後調用一次release或者autorelease
2> 當調用retain方法讓對象的計數器+1,就必須在最後調用一次release或者autorelease
2.集合的內存管理細節
1> 當把一個對象添加到集合中時,這個對象會做了一次retain操作,計數器會+1
2> 當一個集合被銷燬時,會對集合裏面的所有對象做一次release操作,計數器會-1
3> 當一個對象從集合中移除時,這個對象會一次release操作,計數器會-1
3.普遍規律
1> 如果方法名是add\insert開頭,那麼被添加的對象,計數器會+1
2> 如果方法名是remove\delete開頭,那麼被移除的對象,計數器-1
ARC
iOS 5.0之後引用自動管理機制——自動引用計數(ARC),管理機制與手動機制一樣,只是不再需要調用retain、release、autorelease;它編譯時的特性,當你使用ARC時,在適當位置插入release和autorelease;它引用strong和weak關鍵字,strong修飾的指針變量指向對象時,當指針指向新值或者指針不復存在,相關聯的對象就會自動釋放,而weak修飾的指針變量指向對象,當對象的擁有者指向新值或者不存在時weak修飾的指針會自動置爲nil。
循環引用常見問題
類相互引用
- (void)viewDidLoad {
[super viewDidLoad];
ClassA *a = [[ClassA alloc] init];
ClassB *b = [[ClassB alloc] init];
a.classB = b;
b.classA = a;
}
兩個類 A 和 B A強引用B B強引用A 所以下面的代碼 dealloc 不會走 發生循環引用
解決方法 讓其中任意一個類 變成弱引用即可 即 @property (nonatomic, weak) ClassA *classA;
運行結果如下:
2.Block問題
- (void)blockFunc {
self.block = ^{
self.str = @"hello";//發生循環引用
NSLog(@"%@", self.str);
};
self.block();
}
可以發現發生循環引用 解決辦法爲 用weak 進行弱引用
- (void)blockFunc {
__weak typeof(self) weakSelf = self;
self.block = ^{
weakSelf.str = @"hello";
NSLog(@"%@", weakSelf.str);
};
self.block();
}
注:此處爲自己的原創:
==========
/**
第二種方式 此種方法需要確保再self釋放之後不會再有調用當前類的其他方法。
**/
self.nameStr = @"sssss";
__block ViewController2 *bSelf = self;
self.pushBlock = ^{
NSLog(@"name的值是:%@",bSelf.nameStr);
bSelf = nil;
};
self.pushBlock();
/**
第三種方式
將當前對象作爲block的入參傳入到Block內部去
**/
self.nameStr = @"ssss"
self.pushBlock = ^(ViewController2 *obj) {
NSLog(@"name的值是:%@",obj.nameStr);
};
self.pushBlock(self);
=========
原創結束
當在block中添加延遲函數時 下面函數就會出現問題
- (void)blockFunc {
self.str = @"hello";
__weak typeof(self) weakSelf = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", weakSelf.str);
});
};
self.block();
}
1 打印null 5秒之內VC返回 此時VC 頁面消失 會走dealloc函數 故里面的weakSelf會爲空
2 打印hello 5秒之後VC返回 延遲函數已執行完 打印完整的hello VC 消失 走dealloc
解決辦法爲 用strong 進行 強引用
- (void)blockFunc {
self.str = @"hello";
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", strongSelf.str);
});
};
self.block();
}
首先 weakSelf 就是弱引用 防止裏面循環引用 其次 內部的strongSelf是個 局部變量 僅存在於棧中 當strongSelf.str 執行完 strongSelf就會回收 不會造成循環引用 strongSelf 會使vc引用計數器加1 當其返回時 並不會立刻走dealloc函數 而是等strongSelf.str它執行完 引用計數爲0 纔會走dealloc 函數 所以不論什麼時候返回 strongSelf.str都是有值的 這就是爲什麼__weak 和 __strong 成對出現的原因
3.NSTimer
- (void)viewDidLoad {
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerClicked) userInfo:nil repeats:YES];
// 等同於 下面 RunLoop -> timer -> self
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerClicked) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
}
- (void)timerClicked {
NSLog(@"timerClicked ----");
}
這段代碼發生內存泄露 頁面消失 timerClicked函數一直執行
解決辦法爲 didMoveToParentViewController函數 將timer 置爲空 或在合適位置將其timer取消
- (void)didMoveToParentViewController:(UIViewController *)parent {
if (nil == parent) {
[self.timer invalidate];
self.timer = nil;
}
}