iOS的記憶體管理機制 Objective-C在iOS中不支持GC(垃圾回收)機制,而是採用的引用計數的方式管理記憶體。 引用計數:在引用計數中,每一個對象負責維護對象所有引用的計數值。當一個新的引用指向對象時,引用計數器就遞增,當去掉一個引用時,引用計數就遞減。當引用計數到零時,該對象就將釋放占有的資 ...
iOS的記憶體管理機制
Objective-C在iOS中不支持GC(垃圾回收)機制,而是採用的引用計數的方式管理記憶體。
引用計數:在引用計數中,每一個對象負責維護對象所有引用的計數值。當一個新的引用指向對象時,引用計數器就遞增,當去掉一個引用時,引用計數就遞減。當引用計數到零時,該對象就將釋放占有的資源。
以開燈為例:
圖中,“需要照明的人數”即對應我們要說的引用計數值。
- 第一個人進入辦公室,“需要照明的人數”加1,計數值從0變為1,因此需要開燈;
- 之後每當有人進入辦公室,“需要照明的人數”就加1。如計數值從1變成2;
- 每當有人下班離開辦公室,“需要照明的人數”加減1如計數值從2變成1;
- 最後一個人下班離開辦公室時,“需要照明的人數”減1。計數值從1變成0,因此需要關燈。
在Objective-C中,”對象“相當於辦公室的照明設備,”對象的使用環境“相當於進入辦公室的人。上班進入辦公室的人對辦公室照明設備發出的動作,與Objective-C中的對應關係如下表
對照明設備所做的動作 | 對Objective-C對象所做的動作 |
---|---|
開燈 | 生成對象 |
需要照明 | 持有對 |
不需要照明 | 釋放對象 |
|
廢棄對象 |
MRC(Manual Reference Counting)中引起應用計數變化的方法
Objective-C對象方法 | 說明 |
---|---|
alloc/new/copy/mutableCopy | 創建對象,引用計數加1 |
retain | 引用計數加1 |
release | 引用計數減1 |
dealloc | 當引用計數為0時調用 |
[NSArray array] | 引用計數不增加,由自動釋放池管理 |
[NSDictionary dictionary] | 引用計數不增加,由自動釋放池管理 |
ARC(Automatic Reference Counting)中記憶體管理
Objective-C對象所有權修飾符 | 說明 |
---|---|
__strong | 對象預設修飾符,對象強引用,在對象超出作用域時失效。其實就相當於retain操作,超出作用域時執行release操作 |
__weak | 弱引用,不持有對象,對象釋放時會將對象置nil。 |
__unsafe_unretained | 弱引用,不持有對象,對象釋放時不會將對象置nil。 |
__autoreleasing | 自動釋放,由自動釋放池管理對象 |
引用一
1 [self.teacher requestData:^(NSData *data) { 2 self.name = @"case"; 3 }];
此種情況是最常見的迴圈引用導致的記憶體泄露了,在這裡,self強引用了teacher, teacher又強引用了一個block,而該block在回調時又調用了self,會導致該block又強引用了self,造成了一個保留環,最終導致self無法釋放。
self -> teacher -> block -> self
解決方法
1 __weak typeof(self) weakSelf = self; 2 [self.teacher requestData:^(NSData *data) { 3 typeof(weakSelf) strongSelf = weakSelf; 4 strongSelf.name = @"case"; 5 }];
通過__weak的修飾,先把self弱引用(預設是強引用,實際上self是有個隱藏的__strong修飾的),然後在block回調里用weakSelf,這樣就會打破保留環,從而避免了迴圈引用,如下:
self -> teacher -> block -> weakSelf
註意:一般會在block回調里再強引用一下weakSelf(typeof(weakSelf) strongSelf = weakSelf;),因為__weak修飾的都是存在棧內,可能隨時會被系統釋放,造成後面調用weakSelf時weakSelf可能已經是nil了,後面用weakSelf調用任何代碼都是無效的。
引用二
代碼
ViewController.m
1 #import "ViewController.h" 2 #import "HYBAController.h" 3 4 @interface ViewController () 5 6 @property (nonatomic, strong) UIButton *button; 7 @property (nonatomic, strong) HYBAController *vc; 8 9 @end 10 11 @implementation ViewController 12 13 - (void)goToNext { 14 __weak __typeof(self) weakSelf = self; 15 HYBAController *vc = [[HYBAController alloc] initWithCallback:^{ 16 [weakSelf.button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal]; 17 }]; 18 // self.vc = vc; 19 [self.navigationController pushViewController:vc animated:YES]; 20 } 21 22 23 24 - (void)viewDidLoad { 25 [super viewDidLoad]; 26 27 UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 28 [button setTitle:@"進入下一個界面" forState:UIControlStateNormal]; 29 button.frame = CGRectMake(50, 200, 200, 45); 30 button.backgroundColor = [UIColor redColor]; 31 [button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal]; 32 [button addTarget:self action:@selector(goToNext) forControlEvents:UIControlEventTouchUpInside]; 33 [self.view addSubview:button]; 34 self.button = button; 35 36 self.title = @"ViewController"; 37 38 } 39 40 @endView Code
HYBAController.h
1 #import <UIKit/UIKit.h> 2 3 typedef void(^HYBCallbackBlock)(); 4 5 @interface HYBAController : UIViewController 6 7 - (instancetype)initWithCallback:(HYBCallbackBlock)callback; 8 9 @end
HYBAController.m
1 #import "HYBAController.h" 2 #import "HYBAView.h" 3 4 @interface HYBAController() 5 6 @property (nonatomic, copy) HYBCallbackBlock callbackBlock; 7 8 @property (nonatomic, strong) HYBAView *aView; 9 @property (nonatomic, strong) id currentModel; 10 11 @end 12 13 @implementation HYBAController 14 15 - (instancetype)initWithCallback:(HYBCallbackBlock)callback { 16 if (self = [super init]) { 17 self.callbackBlock = callback; 18 } 19 20 return self; 21 } 22 23 - (void)viewDidLoad { 24 [super viewDidLoad]; 25 26 self.title = @"HYBAController"; 27 self.view.backgroundColor = [UIColor whiteColor]; 28 29 __block __weak __typeof(_currentModel) weakModel = _currentModel; 30 self.aView = [[HYBAView alloc] initWithBlock:^(id model) { 31 // 假設要更新model 32 weakModel = model; 33 // self.currentModel = model; 34 }]; 35 // 假設占滿全屏 36 self.aView.frame = self.view.bounds; 37 [self.view addSubview:self.aView]; 38 self.aView.backgroundColor = [UIColor whiteColor]; 39 40 } 41 42 - (void)viewDidAppear:(BOOL)animated { 43 [super viewDidAppear:animated]; 44 45 NSLog(@"進入控制器:%@", [[self class] description]); 46 } 47 48 - (void)dealloc { 49 NSLog(@"%@-->控制器被dealloc", [[self class] description]); 50 } 51 52 @endView Code
HYBAView.h
1 #import <UIKit/UIKit.h> 2 3 typedef void(^HYBFeedbackBlock)(id model); 4 5 @interface HYBAView : UIView 6 7 - (instancetype)initWithBlock:(HYBFeedbackBlock)block; 8 9 @end
HYBAView.m
1 #import "HYBAView.h" 2 3 @interface HYBAView () 4 5 @property (nonatomic, copy) HYBFeedbackBlock block; 6 7 @end 8 9 @implementation HYBAView 10 11 - (void)dealloc { 12 NSLog(@"dealloc: %@", [[self class] description]); 13 } 14 15 - (instancetype)initWithBlock:(HYBFeedbackBlock)block { 16 if (self = [super init]) { 17 self.block = block; 18 19 UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 20 [button setTitle:@"反饋給controller" forState:UIControlStateNormal]; 21 button.frame = CGRectMake(50, 200, 200, 45); 22 button.backgroundColor = [UIColor redColor]; 23 [button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal]; 24 [button addTarget:self action:@selector(feedback) forControlEvents:UIControlEventTouchUpInside]; 25 [self addSubview:button]; 26 } 27 28 return self; 29 } 30 31 - (void)feedback { 32 if (self.block) { 33 // 傳模型回去,這裡沒有數據,假設傳nil 34 self.block(nil); 35 } 36 } 37 38 @endView Code
以上是正常運行,不存在記憶體泄露的代碼,下麵進行細緻討論情況
場景一:Controller之間block傳值
1 @interface ViewController () 2 3 // 引用按鈕只是為了測試 4 @property (nonatomic, strong) UIButton *button; 5 // 只是為了測試記憶體問題,引用之。在開發中,有很多時候我們是 6 // 需要引用另一個控制器的,因此這裡模擬之。 7 @property (nonatomic, strong) HYBAController *vc; 8 9 @end 10 11 // 點擊button時 12 - (void)goToNext { 13 HYBAController *vc = [[HYBAController alloc] initWithCallback:^{ 14 [self.button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal]; 15 }]; 16 self.vc = vc; 17 [self.navigationController pushViewController:vc animated:YES]; 18 }
原因:
這裡形成了兩個環:
-
ViewController->強引用了屬性vc->強引用了callback->強引用了ViewController
-
ViewController->強引用了屬性vc->強引用了callback->強引用了ViewController的屬性button
解決方案:
不聲明vc屬性或者將vc屬性聲明為weak引用的類型,在callback回調處,將self.button改成weakSelf.button,也就是讓callback這個block對viewcontroller改成弱引用。如就是改成如下,記憶體就可以正常釋放了:
1 - (void)goToNext { 2 __weak __typeof(self) weakSelf = self; 3 HYBAController *vc = [[HYBAController alloc] initWithCallback:^{ 4 [weakSelf.button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal]; 5 }]; 6 // self.vc = vc; 7 [self.navigationController pushViewController:vc animated:YES]; 8 }
筆者嘗試過使用Leaks檢測記憶體泄露,但是全是通過,一個綠色的勾,讓你以為記憶體處理得很好了,實際上記憶體並得不到釋放。
針對這種場景,給大家提點建議:
在控制器的生命周期viewDidAppear里列印日誌:
1 - (void)viewDidAppear:(BOOL)animated { 2 [super viewDidAppear:animated]; 3 4 NSLog(@"進入控制器:%@", [[self class] description]); 5 }
在控制器的生命周期dealloc里列印日誌
1 - (void)dealloc { 2 NSLog(@"控制器被dealloc: %@", [[self class] description]); 3 }
場景二: Controller與View之間Block傳值
定義一個view,用於與Controller交互。當點擊view的按鈕時,就會通過block回調給controller,也就反饋到控制器了,並將對應的數據傳給控制器以記錄
1 typedef void(^HYBFeedbackBlock)(id model); 2 3 @interface HYBAView : UIView 4 5 - (instancetype)initWithBlock:(HYBFeedbackBlock)block; 6 7 @end 8 9 @interface HYBAView () 10 11 @property (nonatomic, copy) HYBFeedbackBlock block; 12 13 @end 14 15 @implementation HYBAView 16 17 - (void)dealloc { 18 NSLog(@"dealloc: %@", [[self class] description]); 19 } 20 21 - (instancetype)initWithBlock:(HYBFeedbackBlock)block { 22 if (self = [super init]) { 23 self.block = block; 24 25 UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 26 [button setTitle:@"反饋給controller" forState:UIControlStateNormal]; 27 button.frame = CGRectMake(50, 200, 200, 45); 28 button.backgroundColor = [UIColor redColor]; 29 [button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal]; 30 [button addTarget:self action:@selector(feedback) forControlEvents:UIControlEventTouchUpInside]; 31 [self addSubview:button]; 32 } 33 34 return self; 35 } 36 37 - (void)feedback { 38 if (self.block) { 39 // 傳模型回去,這裡沒有數據,假設傳nil 40 self.block(nil); 41 } 42 } 43 44 @endView Code
HYBAController,增加了兩個屬性,在viewDidLoad時,創建了aView屬性
1 @interface HYBAController() 2 3 @property (nonatomic, copy) HYBCallbackBlock callbackBlock; 4 5 @property (nonatomic, strong) HYBAView *aView; 6 @property (nonatomic, strong) id currentModel; 7 8 @end 9 10 @implementation HYBAController 11 12 - (instancetype)initWithCallback:(HYBCallbackBlock)callback { 13 if (self = [super init]) { 14 self.callbackBlock = callback; 15 } 16 17 return self; 18 } 19 20 - (void)viewDidLoad { 21 [super viewDidLoad]; 22 23 self.title = @"HYBAController"; 24 self.view.backgroundColor = [UIColor whiteColor]; 25 26 self.aView = [[HYBAView alloc] initWithBlock:^(id model) { 27 // 假設要更新model 28 self.currentModel = model; 29 }]; 30 // 假設占滿全屏 31 self.aView.frame = self.view.bounds; 32 [self.view addSubview:self.aView]; 33 self.aView.backgroundColor = [UIColor whiteColor]; 34 } 35 36 - (void)viewDidAppear:(BOOL)animated { 37 [super viewDidAppear:animated]; 38 39 NSLog(@"進入控制器:%@", [[self class] description]); 40 } 41 42 - (void)dealloc { 43 NSLog(@"控制器被dealloc: %@", [[self class] description]); 44 } 45 46 @endView Code
原因,形成的環:
-
vc->aView->block->vc(self)
-
vc->aView->block->vc.currentModel
解決的辦法可以是:在創建aView時,block內對currentModel的引用改成弱引用:
1 __weak __typeof(self) weakSelf = self; 2 self.aView = [[HYBAView alloc] initWithBlock:^(id model) { 3 // 假設要更新model 4 weakSelf.currentModel = model; 5 }];
很多類似這樣的代碼,直接使用成員變數,而不是屬性,然後他們以為這樣就不會引用self,也就是控制器,從而不形成環:
1 self.aView = [[HYBAView alloc] initWithBlock:^(id model) { 2 // 假設要更新model 3 _currentModel = model; 4 }];View Code
這是錯誤的理解,當我們引用了_currentModel時,它是控制器的成員變數,因此也就引用了控制器。要解決此問題,也是要改成弱引用:
1 __block __weak __typeof(_currentModel) weakModel = _currentModel; 2 self.aView = [[HYBAView alloc] initWithBlock:^(id model) { 3 // 假設要更新model 4 weakModel = model; 5 }];View Code
這裡還要加上__block啊!
模擬迴圈引用
假設下麵如此寫代碼,是否出現記憶體得不到釋放問題?(其中,controller屬性都是強引用聲明的)
1 @autoreleasepool { 2 A *aVC = [[A alloc] init]; 3 B *bVC = [[B allcok] init]; 4 aVC.controller = bVC; 5 bVC.controller = aVC; 6 }
分析:
aVC->強引用了bVC->強引用了aVC,因此形成了一個環,導致記憶體得不到釋放。
引用三
註意在UI中,如果一個AViewController中存在,並且存在Block屬性,若AViewController不消失,則整個AViewControlller不會釋放,不會列印dealloc中的東西,所以要想列印需要將AViewController push掉用導航控制器或者模態視圖消失掉!
block中迴圈引用問題
由於block會對block中的對象進行持有操作,就相當於持有了其中的對象,而如果此時block中的對象又持有了該block,則會造成迴圈引用。如下:
typedef void (^ block)(void); @property (copy, nonatomic) block myBlock; @property (copy, nonatomic) NSString *blockString; - (void)viewWillAppear:(BOOL)animated { [super viewDidAppear:animated]; NSLog(@"進入控制器:%@", [[self class] description]); [self testBlock]; } - (void)testBlock { self.myBlock = ^() { //其實註釋中的代碼,同樣會造成迴圈引用 NSString *localString = self.blockString; // NSString *localString = _blockString; // [self doSomething]; }; } - (void)dealloc { NSLog(@"%@--------->控制器被dealloc", [[self class] description]); }
列印結果:
2018-03-04 20:57:57.890402+0800 BlockTest[2547:345760] 進入控制器:NextViewController
當有someObj持有self對象,此時的關係圖如下。
當someObj對象release self對象時,self和myblock相互引用,retainCount都為1,造成迴圈引用
註意:
註釋掉的代碼同樣會造成迴圈引用,因為不管是通過self.blockString
還是_blockString
,或是函數調用[self doSomething]
,因為只要 block中用到了對象的屬性或者函數,block就會持有該對象而不是該對象中的某個屬性或者函數。
解決方法:
1 __weak typeof(self) weakSelf = self; 2 self.myBlock = ^() { 3 NSString *localString = weakSelf.blockString; 4 };
使用__weak
修飾self,使其在block中不被持有,打破迴圈引用。開始狀態如下
當someObj對象釋放self對象時,Self的retainCount為0,走dealloc,釋放myBlock對象,使其retainCount也為0。
多個對象發生迴圈引用
以上迴圈引用的情況很容易發現,因為此時Xcode就會報警告。而發生在多個對象間的時候,Xcode就檢測不出來了,這往往就容易被忽略。
1 //ClassB 2 @interface ClassB : NSObject 3 @property (strong, nonatomic) ClassA *objA; 4 - (void)doSomething; 5 @end 6 7 //ClassA 8 @property (strong, nonatomic) ClassB *objB; 9 @property (copy, nonatomic) block myBlock; 10 11 - (void)testBlockRetainCycle { 12 ClassB* objB = [[ClassB alloc] init]; 13 self.myBlock = ^() { 14 [objB doSomething]; 15 }; 16 objB.objA = self; 17 }
解決方法:
1 - (void)testBlockRetainCycle { 2 ClassB* objB = [[ClassB alloc] init]; 3 __weak typeof(objB) weakObjB = objB; 4 self.myBlock = ^() { 5 [weakObjB doSomething]; 6 }; 7 objB.objA = self; 8 }
將objA對象weak,使其不在block中被持有
註:以上使用__weak
打破迴圈的方法只在ARC下才有效,在MRC下應該使用__block
或者,在block執行完後,將block置nil,這樣也可以打破迴圈引用
1 - (void)testBlockRetainCycle { 2 ClassB* objB = [[ClassB alloc] init]; 3 self.myBlock = ^() { 4 [objB doSomething]; 5 }; 6 objA.objA = self; 7 self.myBlock(); 8 self.myBlock = nil; 9 }