在Build Phases -> Compile Sources -> 對應的文件加上-fno-objc-arc的編譯參數可以啟用MRC模式
1.簡介
1.1 內存管理的思考方式
- 自己生成的對象,自己持有
- 非自己生成的對象,自己也能持有(指針指向這個對象,引用計數+1,就是持有)
- 不再需要自己持有的對象時釋放
- 非自己持有的對象無法釋放
1.2 ARC下的內存管理
ARC雖然能夠解決90%的內存管理問題,另外還有10%的需要開發者自己處理
- 過度使用block,無法解決循環引用問題
- 遇到底層Core Foundation對象,需要自己手動管理它們的引用計數時
2.引用計數(Reference Count)
2.1 什么是引用計數
引用計數是一種管理對象生命周期的方式,一個內存塊被強指針指向的數量。
2.2 alloc/retain/release/dealloc
cocoa框架中foundation框架類庫的NSObject類是擔負著內存管理的
- alloc: 生成并持有對象。
自己持有的對象可以使用alloc/new/copy/mutableCopy開頭命名,如果自己不持有不能是有這些作為開頭。 - retain:持有對象。
引用計數+1,超過最大數時會報錯。尋址找到對象內存地址頭部,減去存放引用計數值的struct的大小的地址,獲取到計數值并+1,當計數值為超過最大值時拋出異常代碼。 - release:釋放對象。
引用計數-1,如果釋放了非自己持有的對象或者釋放了引用計數為0的對象,程序會崩潰。尋址找到對象內存地址頭部,減去存放引用計數值的struct的大小的地址,獲取到計數值并-1,當計數值為0時調用dealloc方法。 - dealloc:廢棄對象。
廢棄由alloc開辟的內存塊
指向一個內存塊的每一條指針,都能使用retain和release對這個內存塊的引用計數進行改變。
//開辟內存塊,引用計數為1
id object1 = [[NSObject alloc] init];
//object2的指針指向object1創建的內存塊,內存塊的引用計數還是1
id object2 = object1;
//object2使用retain是引用計數+1
[object2 retain];
//object1使用release將內存塊上的引用計數-1
[object1 release];
//object1還是可以使用release將內存塊的引用計數再-1
//此時內存塊上的引用計數值為0,內存空間被系統回收
//tips:系統知道馬上就要回收內存了,沒必要-1了,直接回收對象
[object1 release];
如果想要銷毀一個對象,不僅需要使用release將引用計數-1,還需要將對象的指針置為nil
id object1 = [[NSObject alloc] init];
[object1 release];
object1 = nil;
2.3 引用計數的實現
GNUstep中的實現:采用在內存塊頭部放置引用計數來進行管理
- 分配存放對象所需要的內存空間,將該內存空間置0
- 用一個整數來記錄retain的引用計數值,并把這個整數寫到內存空間的頭部
- 返回對象指針
GNUstep優點:
- 少量代碼就能完成
- 能夠統一管理引用計數用的內存塊和對象用內存塊
蘋果源碼中的實現:采用引用技術表(散列表)管理引用計數。
- 分配存放對象所需要的內存空間,將空間置0
- 在散列表中加入引用計數并附帶對象的內存塊地址
- 返回對象指針
蘋果源碼優點:
- 對象用內存塊分配無需考慮內存塊頭部
- 引用技術表各記錄中存有內存塊地址,可從各個記錄追溯到各個內存塊地址,在調試時,只要引用計數表沒有被破壞,就可以確認內存塊位置,就可以檢測各對象持有者是否存在。
2.4 別向已經釋放的對象發消息
當一個對象引用計數為0時,系統已經將該對象的地址回收,它的輸出結果是不一定的,如果被回收的地址為空,則會輸出0,如果地址被內存復用了,就會造成系統崩潰。
NSObject *object = [[NSObject alloc] init];
NSLog(@"Reference Count = %u", [object retainCount]);
[object release];
//此時object的內存被釋放了,輸入為0或者崩潰
NSLog(@"Reference Count = %u", [object retainCount]);
3.循環引用(Reference Cycle)
如果對象A的成員變量是對象B,對象B的成員變量是A,釋放對象A需要先釋放成員變量B,而釋放成員變量B也需要先釋放成員變量A,形成一個無法釋放的循環,造成內存泄漏,這就是循環引用。
- (void)viewDidLoad {
[super viewDidLoad];
CustomView *view = [[CustomView alloc] init];
[self.view addSubview:view];
view.callbackBlock = ^{
//view持有的block中調用了self,意味著view成員變量的一根指針指向了self
[self doSomething];
}
}
當 viewDidLoad 方法執行時,創建一個 block 并賦值給對象 view 的 callbackBlock 屬性,callbackBlock捕捉 self,self 持有 self.view, v 在 addSubview 后成為 self.view 的子 view 而被 self.view 持有,這樣就形成了一個引用循環,self -> self.view -> view -> callBackBlock -> self。
環路越是大的循環引用,越難以被發現。
打破循環方法1:主動斷開循環引用
根據業務邏輯主動斷開引用,在view 執行完callbackBlock后,將block置為nil,主動斷開循環引用。
if (self.callbackBlock) {
self.callbackBlock();
//調用方將指向callbackBlock的指向指向了nil,主動釋放block
self.callbackBlock = nil;
}
循環變成在view -> callBackBlock處被主動斷開,循環引用打破
解決方法2:弱引用
以代理模式為例,對象A中創建對象B并拿到它的delegate,如果delegate為strong聲明的,則會形成循環引用A -> B -> BDelegate -> A,而事實上delegate對象通常都被聲明為weak型變量,就是為了避免循環引用,從BDdelegate -> A處打破循環引用。
系統對于每一個有弱引用的對象,都維護一個表來記錄它所有的弱引用的指針地址。每當一個對象的引用計數為 0 時,系統就通過這張表,找到指向對象的所有弱引用指針,把它們置成 nil。
weakSelf 和 strongSelf:
weakSelf是將一根weak聲明的指針指向了self,在self -> self.view -> view -> callBackBlock -> self循環中,weakSelf使用弱引用的方式從callBackBlock -> self處打破循環引用。
- (void)viewDidLoad {
[super viewDidLoad];
CustomView *view = [[CustomView alloc] init];
[self.view addSubview:view];
__weak __typeof__(self) weakSelf = self;
view.callbackBlock = ^{
[weakSelf doSomething];
}
}
但是使用weakSelf存在一個 callbackBlock 執行時self對象會釋放的問題,如果在剛剛執行callbackBlock 中的方法時 weakSelf 就為 nil了,那么callbackBlock中的所有方法中的weakSelf都會是nil,callbackBlock的輸出結果是唯一的,不會造成什么影響。但是如果self是在callbackBlock執行到一半的時候釋放的,就會導致 callbackBlock出現多種不同的執行結果,這種時候就需要利用strongSelf來保證在所在block的作用域中self不被釋放。
- (void)viewDidLoad {
[super viewDidLoad];
CustomView *view = [[CustomView alloc] init];
[self.view addSubview:view];
//將指向self的指針變成弱指針,這樣不會造成循環引用
__weak __typeof__(self) weakSelf = self;
view.callbackBlock = ^{
//保證在所在block的作用域中self不被釋放
//如果不加strongSelf,可能會出現在doSomething時self還存在,而在執行doAnoterThing時,self變成了nil
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doAnoterThing];
}
}
在嵌套block中,每個block中都需要設置strongSelf。
DemoViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
CustomView *view = [[CustomView alloc] init];
[self.view addSubview:view];
//將指向self的指針變成弱指針,這樣不會造成循環引用
__weak __typeof__(self) weakSelf = self;
view.callbackBlock = ^{
//保證在callbackBlock作用域內的self一直不釋放
//如果self進入block時就為nil,則一直為nil。
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doAnoterThing];
obj.objCallbackBlock = ^{
//需要重新設置strongSelf,保證在objCallbackBlock作用域內的self一直不釋放
__typeof__(self) strongSelf = weakSelf;
[strongSelf doObjSomething];
[strongSelf doObjAnotherThing];
}
}
}
4.Core Foundation
- __bridge
只做類型轉換,不修改相關對象的引用計數,原來的 Core Foundation 對象在不用時,需要調用 CFRelease 方法。- Core Foundation對象 -> Objective-C對象
NSMutableString *mString;
CFMutableStringRef cfstr;
{
//通過CFCreate系列方法創建,內存塊引用計數為1
CFMutableStringRef cfstring = CFStringCreateMutable(kCFAllocatorDefault, 1);
//由于mString是__strong修飾符修飾的,mString指向內存快時引用計數++
//引用計數為2
mString = (__bridge NSMutableString *)cfstring;
cfstr = cfstring;
NSLog(@"retain count = %ld",CFGetRetainCount(cfstring));
}
//由于CF對象不會自動釋放,所以引用計數為2
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr)); - Objective-C對象 -> Core Foundation對象
CFMutableStringRef cfstr;
{
//通過alloc方法創建內存塊,內存塊引用計數為1
NSMutableString *mString = [[NSMutableString alloc] init];
//通過__bridge轉換,內存塊上的引用計數不變,依舊為1
cfstr = (__bridge CFMutableStringRef)mString;
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
}
//內存塊通過Arc機制釋放,cfstr所指為野指針,崩潰或為未知對象
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
- Core Foundation對象 -> Objective-C對象
- __bridge_retained
類型轉換后,將相關對象的引用計數加 1,原來的 Core Foundation 對象在不用時,需要調用 CFRelease 方法。Core Foundation對象 -> Objective-C對象 ERROR
NSMutableString *mString;
CFMutableStringRef cfstr;
{
//通過CFCreate系列方法創建,內存塊引用計數為1
CFMutableStringRef cfstring = CFStringCreateMutable(kCFAllocatorDefault, 1);
//由于mString是__strong修飾符修飾的,mString指向內存快時引用計數++
//但由于是__bridge_retained,引用計數又會+1,在ARC下編譯會報錯
mString = (__bridge_retained NSMutableString *)cfstring;
cfstr = cfstring;
NSLog(@"retain count = %ld",CFGetRetainCount(cfstring));
}
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));Objective-C對象 -> Core Foundation對象
CFMutableStringRef cfstr;
{
//通過alloc方法創建內存塊,內存塊引用計數為1
NSMutableString *mString = [[NSMutableString alloc] init];
//因為是__bridge_retained 所以引用計數++
//內存塊引用計數為2
cfstr = (__bridge_retained CFMutableStringRef)mString;
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
}
//ARC下出了作用域,mString被釋放,引用計數--
//引用計數為1
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
- __bridge_transfer
類型轉換后,將該對象的引用計數交給 ARC 管理,Core Foundation 對象在不用時,不再需要調用 CFRelease 方法。Core Foundation對象 -> Objective-C對象
NSMutableString *mString;
CFMutableStringRef cfstr;
{
//通過CFCreate系列方法創建,內存塊引用計數為1
CFMutableStringRef cfstring = CFStringCreateMutable(kCFAllocatorDefault, 1);
//因為是__bridge_transfer 所以引用計數交由OC對象管理
//引用計數為1 不變
mString = (__bridge_transfer NSMutableString *)cfstring;
cfstr = cfstring;
NSLog(@"retain count = %ld",CFGetRetainCount(cfstring));
}
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));Objective-C對象 -> Core Foundation對象 ERROR
CFMutableStringRef cfstr;
{
//通過alloc方法創建內存塊,內存塊引用計數為1
NSMutableString *mString = [[NSMutableString alloc] init];
//因為是__bridge_transfer 所以引用計數交由CF對象管理
//在ARC下編譯會報錯
cfstr = (__bridge_transfer CFMutableStringRef)mString;
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
}
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
- 總結
- Objective-C對象 -> Core Foundation對象用__bridge或__bridge_retain。
- Core Foundation對象 -> Objective-C對象用__bridge或__bridge_transfer。