IOS內(nèi)存管理機制

一、MRC(手動引用計數(shù)):

不像 java 有垃圾回收機制,Objective-C 繼承于 C ,使用一套基于對象引用計數(shù)的內(nèi)存管理體系來進行內(nèi)存管理。主要使用 retain、release 和 autorelease 進行引用計數(shù)操作。

原理:

1)每個對象內(nèi)部都保存了一個與之相關(guān)聯(lián)的整數(shù),稱為引用計數(shù)器(reference count)

2)每當使用 alloc、new 或者 copy 創(chuàng)建一個對象時,對象的引用計數(shù)器被設(shè)置為 1

3)給對象發(fā)送一條 retain 消息(即調(diào)用 retain 方法),可以使引用計數(shù)器值 +1

4)給對象發(fā)送一條 release 消息,可以使引用計數(shù)器值 -1

5)當一個對象的引用計數(shù)器值為 0 時,那么它將被銷毀,runtime 會自動向該對象發(fā)送一條 dealloc 消息來回收它所占用的內(nèi)存。一般會重寫 dealloc 方法,在這里釋放相關(guān)資源。

6)可以給對象發(fā)送 retainCount 消息獲得當前的引用計數(shù)器值。

原則:

1)對象引用計數(shù)只適用于繼承于 NSObject 對象的 Objective-C 對象,一般數(shù)據(jù)類型不需要使用。

2)誰創(chuàng)建,誰釋放。如果你通過 alloc、new 或者 ( mutable )copy 來創(chuàng)建一個對象,那么你必須調(diào)用 release 或 autorelease ?;蚓湓捳f,不是你創(chuàng)建的,就不用你去釋放。

3)一般來說,除了 alloc、new 或 copy 之外的方法創(chuàng)建的對象都被聲明了 autorelease( autorelease 是延遲釋放內(nèi)存,不用你自己去手動釋放,系統(tǒng)會知道在什么時候該去釋放掉它)。

4)誰 retain,誰 release。只要你調(diào)用了 retain,無論這個對象是如何生成的,你都要調(diào)用 release。

所以,使用 MRC 時,聲明 property 時,通常使用 retain、copy 和 assign 三個修飾符。

assign:不進行引用計數(shù),如果用于 Objective-C 對象,只是簡單進行賦值,不進行 retain 操作。還可以用于聲明常規(guī)數(shù)據(jù)類型的屬性。

retain:用于 Objective-C object,使用后原 object 的 retainCount +1,即只是進行指針復制(淺復制)。

copy:用于 Objective-C object,使用后,原 object 不變(包括 retainCount),新建一個 object 并且 retainCount 為 1,即進行深復制(前提是該 object 必須實現(xiàn) NSCoping 協(xié)議)。

如果一個 property 使用 retain 進行聲明,它的真實的實現(xiàn)應(yīng)該是這樣:

比如 property 是 obj:

//getter

-(ObjectClass *)obj

{

return obj;

}

//setter

- (void)setObj:(ObjectClass *)obj

{

if(_obj != obj)

{

[_obj release];

_obj = [obj retain];

}

}

現(xiàn)在,當在 .m 文件中使用 @synthesize obj 時,編譯器會自動按上面的方式實現(xiàn) setter 方法。

二、引入 autorelease pool:

后來出現(xiàn)了 autorelease pool,這個機制的出現(xiàn)是基于一種十分常見但很難處理的場景:

需要在一個方法中創(chuàng)建并返回一個對象,比如:

- (ClassA *)func

{

ClassA *obj = [[ClassA alloc] init];

return obj;

}

那么問題來了,是創(chuàng)建者釋放還是調(diào)用者釋放新建的對象呢?如果創(chuàng)建者釋放,怎么知道調(diào)用者什么時候結(jié)束調(diào)用?如果調(diào)用者釋放,又會因為協(xié)同開發(fā)的原因,造成調(diào)用者忘記釋放的情況。

所以 Objective-C 發(fā)明了 autorelease pool 的概念,上面的代碼可以這樣寫:

- (ClassA *)func

{

ClassA *obj = [[][ClassA alloc] init] autorelease];

return obj;

}

autorelease 可以理解為延遲釋放,向每一個對象發(fā)送 autorelease 后,系統(tǒng)都會將該對象放入當前的 autorelease pool,當該 autorelease pool 被釋放時,autorelease pool 會向其中的所有 object 發(fā)送 release 消息。

在 iPhone 項目中,默認都有一個 autorelease pool,程序開始時創(chuàng)建,程序退出時銷毀,按照對 autorelease 的理解,豈不是所有 autorelease pool 中的對象都要等到程序退出時才 release,這樣跟內(nèi)存泄露有什么區(qū)別?

其實,每個程序中的 autorelease pool 是嵌套結(jié)構(gòu)的,即一個 autorelease pool 內(nèi)可以嵌套其他的 autorelease pool,對于每個 Runloop,系統(tǒng)會隱式創(chuàng)建一個 autorelease pool,這樣所有的 autorelease pool 就構(gòu)成一個像 callstack 一樣的棧式結(jié)構(gòu),在 Runloop 結(jié)束時,當前棧頂?shù)?autorelease pool 會被銷毀,這樣這個 pool 中的每一個 object 會被 release。

什么是 Runloop 呢?一個 UI 事件、timer call、delegate call 都是一個新的 Runloop。例如:

- (IBAction)buttonClicked

{

...

}

- (void)applicationWillTerminate:(UIApplication *)application

{

...

}

都是一個新的 Runloop。

三、ARC(自動引用計數(shù)):

ARC 是 iOS 5.0 提出的,ARC 還是利用引用計數(shù)機制進行內(nèi)存管理,只不過開發(fā)者不需要使用 retain 和 release 來對 retainCount 進行顯示的加減,而是由編譯器在編譯的時候自動添加這些代碼,而且生成合適的 dealloc 方法。

在 ARC 模式下是不能使用 retain、release 和 autorelease 關(guān)鍵字的。這樣在聲明 property 的時候就引入了 strong、weak 和 unsafe_unretained 修飾。

strong:用于 Objective-C object,且 object 的 retainCount 會自動 +1,進行淺復制。

weak:用于 Objective-C object,且 object 的 retainCount 不會自動 +1,如果 object 釋放,weak 指針會自動指向 nil。

unsafe_unretained:用于 Objective-C,object 的 retainCount 不會自動 +1,且 object 釋放后,unsafe_unretained 指針也不會指向 nil,會成為野指針。

比如:

情況一,

@property (nonatomic, strong) NSDictionary *firstDict;

@property (nonatomic, strong) NSDictionary *secondDict;

.

self.firstDict = @{@"key":@"value"};

self.secondDict = self.firstDict;

self.firstDict = nil;

NSLog(@"second dict = %@", self.secondDict);

結(jié)果:second dict = @{@"key":@"value"}

情況二,

@property (nonatomic, strong) NSDictionary *firstDict;

@property (nonatomic, weak) NSDictionary *secondDict;

.

self.firstDict = @{@"key":@"value"};

self.secondDict = self.firstDict;

self.firstDict = nil;

NSLog(@"second dict = %@", self.secondDict);

結(jié)果:second dict = null

情況三,

@property (nonatomic, strong) NSDictionary *firstDict;

@property (nonatomic, unsafe_unretained) NSDictionary *secondDict;

.

self.firstDict = @{@"key":@"value"};

self.secondDict = self.firstDict;

self.firstDict = nil;

NSLog(@"second dict = %@", self.secondDict);

結(jié)果:會 crash

注:strong、weak 和 unsafe_unretained 是 property 的修飾符。

變量的修飾符對應(yīng)為:

__strong、__weak__unsafe_unretained__autoreleasing,它們被稱為 lifetime 修飾符。

__strong:變量默認是__strong,只要對象還有強引用,該對象就“活著”。

__weak:只是簡單引用,weak 對象將被設(shè)置為 nil,當對象沒有任何強引用的時候。

__unsafe_unretained:只是簡單引用,但是不設(shè)置為 nil,當對象沒有任何強引用的時候。__unsafe_unretained 對象將會產(chǎn)生野指針。

__autoreleasing:用于標識 id* 的引用參數(shù),或者需要自動釋放的返回對象。

lifetime 修飾符的使用:

正確格式:

類名* 修飾符 變量名

使用時注意事項:

1)使用 __weak 問題

NSString * __weak string = [[NSString alloc]? ? initWithFormat:@"First Name: %@", [self firstName]];

NSLog(@"string: %@", string);

這樣盡管 string 在初始化后被使用,但是,在賦值的時候沒有強引用,因此它將立即被銷毀。

2)循環(huán)引用問題

當涉及到 parent-to-child 與 child-to-parent 引用時,通常 parent-to-child 是 strong,child-to-parent 是 weak 來避免循環(huán)引用。

在類中是用 block,避免類與 block 循環(huán)引用,通常新建一個 __weak 變量指向該類的實例變量,從而在 block 中使用該實例。

3)在 ARC 模式下,不能使用 NSAutoreleasePool 來管理 autorelease pools,而是使用 @autoreleasepool 代替它。

@autoreleasepool 比 NSAutoreleasePool 更有效率,進入時,pool 被 push,正常退出時 pool 會被 poped 出來。如果代碼異常退出,pool 不會 pop 出來。

4)開發(fā)者可以在編譯選項中指定每個文件是否使用 ARC 模式,即使用編譯 flag:-fobjc-arc 與 -fno-objc-arc 。

5)ARC 只對 Objective-C 有效,在 ARC 模式下,編譯器是不會自動管理 Core Foundation 中對象 和 malloc( ) 對象的生命周期的,所以開發(fā)者必須使用 CFRetain 和 CFRelease 來手動管理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容