内存管理(一)MRC
內存管理(一)MRC
管的誰
在Objective-C中創建的對象都分配在堆區,內存管理針對的也是這塊區域。
引用計數
Objective-C內存管理的核心其實引用計數,引用計數(Reference Count)是一個簡單而有效的管理對象生命周期的方式。當我們創建一個新對象的時候,它的引用計數為 1,當有一個新的指針指向這個對象時,我們將其引用計數加 1,當某個指針不再指向這個對象時,我們將其引用計數減 1,當對象的引用計數變為 0 時,說明這個對象不再被任何指針指向了,這個時候我們就可以將對象銷毀,回收內存
Objective-C有兩種內存管理機制:手動管理(MRC)和自動管理(ARC)。目前基本上開發用的都是ARC。最開始學習iOS的時候也用過MRC,先介紹下MRC的機制。
MRC
操作對象的四種方式:
- 生成并持有對象:alloc/new/copy/mutableCopy等, retainCount :+1
- 持有對象:retain,retainCount :+1
- 釋放對象:release,retainCount :-1
- 廢棄對象:dealloc, 自動釋放內存
內存管理的四個法則:
- 自己生成的對象,自己持有
- 非自己生成的對象,自己也能持有
- 不再需要自己持有對象的時候釋放對象
- 非自己持有的對象無法釋放
示例代碼
自己生成的對象,自己持有:
以 alloc/new/copy/mutableCopy 等方法創建的對象歸調用者持有
- (void)test1 {id obj0 = [NSObject alloc]; // 創建一個NSObject對象返回給變量obj, 并且歸調用者持有NSLog(@"obj0 引用計數%ld",CFGetRetainCount((__bridge CFTypeRef)obj0)); }輸出
obj0 引用計數1分析
創建一個NSObject對象返回給變量obj, 并且歸調用者持有
alloc創建對象的過程看我這篇文章
非自己生成的對象,自己也能持有:
alloc/new/copy/mutableCopy 等方法以外的方式創建的對象不歸調用者持有
- (void)test2 {id obj = [NSMutableArray array];id obj2 = [obj retain];NSLog(@"obj2 引用計數%ld",CFGetRetainCount((__bridge CFTypeRef)obj2)); }輸出
obj2 引用計數2分析
非自己生成的對象,且該對象存在是通過autorelease來實現的。autorelease提供了一種使得對象在超出生命周期后能正確的被釋放(通過調用release方法)機制,以便于將對象返回給調用者,讓調用者持有后再釋放對象。否則對象還沒來得及被調用者持有就被系統釋放了。調用autorelease后對象不會立刻被釋放,而是被注冊到autoreleasepool中,然后當autoreleasepool結束被銷毀的時候,才會調用對象的release方法釋放對象
不在需要自己持有的對象時釋放
- (void)test3 {Person *p = [[Person alloc] init];[p release]; }非自己持有的對象無法釋放:
由于當前的調用者并不持有該對象,不能進行釋放操作,否則導致程序崩潰
- (void)test4 {// 由于當前的調用者并不持有改對象,不能進行釋放操作,否則導致程序崩潰。 // 如果要釋放該對象,需要先對對象進行retain操作。id obj = [NSMutableArray array];[obj release]; }代碼改成這樣
注意:如果返回給obj的是NSMutableArray對象,會導致程序崩潰,但是如果是NSArray就不會
- (void)test5 {id obj = [NSMutableArray array];[obj retain]; // 當前調用者obj持有NSMutableArray對象NSLog(@"obj 引用計數%ld",CFGetRetainCount((__bridge CFTypeRef)obj));[obj release]; }分析
如果要釋放該對象,需要先對對象進行retain操作
屬性的引用計數情況
定義一個Person類
代碼如下
VC代碼
@interface VC2 () @property (nonatomic, copy) NSArray *array; @property (nonatomic, retain) Person *per; @end- (void)viewDidLoad {[super viewDidLoad];self.view.backgroundColor = [UIColor orangeColor];[self test6]; } - (void)test6 {self.array = [[NSArray alloc] initWithObjects:@1, nil];NSLog(@"array 引用計數%ld",CFGetRetainCount((__bridge CFTypeRef)_array)); }輸出
array 引用計數2上面不是說過 alloc創建一個對象并且返回給調用者持有 引用計數為1嗎?
這里為什么此時對象的引用計數是2呢?
分析
了解屬性的細節 看我這篇文章
OC語言中 .語法 其實就是調用setter方法
@property (nonatomic, retain) Person *per;
定義per 屬性時候 是retain關鍵字 所以生成的標準setter方法內部會進行retain操作
所以此時對象的內存引用情況是:alloc創建時retainCount為1,setter方法中retain了一次引用計數加1,所以此時retainCount變為了2
類似于如下操作:
NSArray *temp = [[NSArray alloc] initWithObjects:@1, nil]; 引用計數+1 self.array = temp; 引用計數+1所以一般在使用屬性賦值的時候一般這么寫:
用autorelease抵消一次retain操作
或者這樣
NSArray *temp = [[NSArray alloc] initWithObjects:@1, nil]; self.array = temp; [temp release];理解了上面,我們用Person類 觀察下有沒有內存泄漏
- (void)viewDidLoad {[super viewDidLoad];self.view.backgroundColor = [UIColor orangeColor];[self test7]; }- (void)test7 {self.per = [[[Person alloc] init] autorelease];// alloc1次 setter方法一次 所以是2 // autorelease在NSLog時候 還沒有釋放Person對象 所以還是2,但是終究會 -1NSLog(@"per 引用計數%ld",CFGetRetainCount((__bridge CFTypeRef)_per)); }// autorelease1次 VC delloc中 1次 所以Person對象最終會被釋放 - (void)dealloc {[_per release];NSLog(@"%s",__func__);[super dealloc]; }輸出
per 引用計數2 [Person dealloc] [VC2 dealloc]分析
都能夠正常銷毀,不存在內存泄漏
autorelease延遲釋放抵消一次 放棄Person所有權
生成的標準的setter方法,不會自動的在dealloc中生成release的代碼,所以要手動的重寫dealloc方法,加上release的代碼
VC delloc中 在發送一條 release 所以Person對象最終會被銷毀
為了防止野指針, 可以加上nil
大家一定要搞清楚什么是野指針 本篇簡單說下野指針和僵尸對象 詳細的底層原理可以看我這篇文章
野指針和僵尸對象
野指針
指向一個已經被刪除的對象或者訪問受限內存區域的指針就是野指針,野指針不是nil指針,而是指向了垃圾內存的指針
野指針的場景:
1、 對象釋放后,指針沒有置空
- 使用unsafe_unretained修飾符,對象釋放后,沒有手動置為nil
- KVO沒有移除觀察者
2、對象提前釋放
- 異步函數中block使用的self沒有強引用,導致外部已經釋放掉,但是里面還在使用
3、對象多次釋放
- 多個線程同時對某個對象賦值但沒有加鎖,就可能多次release
僵尸對象
一個已經被釋放掉的對象就是僵尸對象
一個OC對象的引用計數為0,調動dealloc后銷毀之后,就是僵尸對象。
一個對象雖然被銷毀掉了,但是數據依然在內存中,所以如果通過野指針去訪問僵尸對象,一旦這個僵尸對象的內存已經被分配給其他人了,就會出錯。
為什么不開啟僵尸對象檢測?
這樣每次通過指針訪問對象的時候都會檢查是否為僵尸對象,這樣很影響效率
為什么每次不去把內存上的數據清零?
沒必要,影響效率, 數據每次都是覆蓋。
例子:
@interface Person : NSObject @property(nonatomic, strong)NSString *name; @end@implementation Person- (void)dealloc {NSLog(@"%s",__func__);[super dealloc]; } @end@implementation ViewController// crash - (void)viewDidLoad {[super viewDidLoad];Person *person = [[Person alloc] init];Person *__unsafe_unretained wp = person;person.name = @"yang";[person release];NSLog(@"wp==%@",wp.name); }輸出崩潰
Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
分析
wp指針弱引用[Person alloc] 對象 不影響對象釋放
0x600000e683e0對象已經釋放了 但是wp指針變量還指向0x600000e683e0對象 0x600000e683e0現在就屬于僵尸對象 wp就是野指針 會造成程序崩潰
把指針設為nil之后
- (void)viewDidLoad {[super viewDidLoad];Person *person = [[Person alloc] init];Person *__unsafe_unretained wp = person;person.name = @"yang";[person release];person= nil;wp = nil;NSLog(@"wp==%@",wp.name); }輸出
-[Person dealloc] wp==(null)或者使用 __weak
- (void)viewDidLoad {[super viewDidLoad];Person *person = [[Person alloc] init];Person *__weak wp = person;person.name = @"yang";[person release];person= nil;NSLog(@"wp==%@",wp.name); }輸出
(lldb) p wp (Person *) $0 = 0x00006000018487e0 -[Person dealloc] (lldb) p wp (Person *) $1 = nil (lldb)兩種方式都可以保證程序正常運行
思考個問題
Q: 既然 __weak 更安全,那么為什么已經有了 __weak 還要保留 __unsafe_unretained ?
1、__weak僅在ARC中才能使用,而MRC只能使用__unsafe_unretained
2、__weak對性能會有一定的消耗,當一個對象dealloc時,需要遍歷對象的weak表,把表里的所有weak指針變量值置為nil,指向對象的weak指針越多,性能消耗就越多。所以__unsafe_unretained比__weak快。當明確知道對象的生命周期時,選擇__unsafe_unretained會有一些性能提升。
比如,MyViewController 持有 MyView,MyView 需要調用 MyViewController 的接口。MyView 中就可以存儲__unsafe_unretained MyViewController *_viewController
對于__weak底層源碼分析可以看我這篇文章
下篇文章我們繼續總結ARC
后記
記得在《尋夢環游記》里對于一個人的死亡是這樣定義的:當這個這個世界上最后一個人都忘記你時,就迎來了終極死亡。類比于引用計數,就是每有一個人記得你時你的引用計數加1,每有一個人忘記你時,你的引用計數減1,當所有人都忘記你時,你就消失了,也就是從內存中釋放了。
如果再深一層,包含我們后面要介紹的ARC中的強引用和弱引用的話,那這個記住的含義就不一樣了。強引用就是你摯愛的親人,朋友等對你比較重要的人記得你,你的引用計數才加1。
而弱引用就是那種路人,一面之緣的人,他們只是對你有一個印象,他們記得你是沒有用的,你的引用計數不會加1。當你摯愛的人都忘記你時,你的引用計數歸零,你就從這個世界上消失了,而這些路人只是感覺到自己記憶中忽然少了些什么而已。
外鏈
1、iOS屬性的底層原理
2、__weak實現底層原理
3、野指針僵尸對象底層原理
總結
以上是生活随笔為你收集整理的内存管理(一)MRC的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BERT-MRC:统一化MRC框架提升N
- 下一篇: 4g网络设置dns地址_如果你的手机Wi