iOS目前已經是ARC 時代。但對于要想了解ARC的內存管理機制,還是依舊需要對MRC時代的內存管理機制有深刻的理解才能掌握ARC。
話不多說,直接開干。
第一點
我們在創建一個對象的時候,不論是ARC 還是MRC 一般都是這樣創建的
NSMutableArray aArray = [[NSArray alloc] init];
NSMutableArray aArray = aArray.mutableCopy;
(一般情況下: 后面會討論例外情況)
alloc 對象分配空間后,引用計數為1
retain 對象的引用計數+1,不會對對象分配空間
copy copy 一個對象變成新的對象(新內存地址) 引用計數為1 原來對象計數不變(就是說,copy是將copy的對象復制一份,作為新的對象,然后分配空間,賦予新地址,再將其引入計數+1)
release 對象的引用計數-1 如果為該對象的引入計數減到 0 時,該對象釋立即放內存
autorelease 對象引用計數-1 如果為0不馬上釋放,最近一個個pool時釋放
NSLog(@"sMessage retainCount:%u",[sMessage retainCount]); //打印對象引入計數的個數
第二點(內存管理的原則)
內存管理的原則 就是最終的引用計數要平衡如果最后引用計數大于0 則會內存泄露
__weak typeof(self) weakSelf = self
[MM_Factory addLeftButtonForVC:self imgName:@"left_mm_oa" hightligthImgName:nil title:nil clickedHandler:^{
[weakSelf.navigationController popViewControllerAnimated:YES];
}];
如果這里的weakSelf 還是self 則就會造成引入計數加1(block中如果不作為copy處理,會造成原有對象引入技術加1)所以會造成內存泄露。
如果引用 計數等于 0 還對該對象進行操作,則會出現內存訪問失敗,crash 所以盡量設置為nil
這兩個問題都很嚴重,所以請一定注意內存釋放和不用過后設置為nil
第三點 (成員變量與屬性)
實際情況并非上面那么簡單,你可能需要在一個函數里調用另一個函數分配的變量這時候有兩個選擇: 類成員變量和使用屬性
@interface TestMem: NSObject {
TestObject *m_testObject ; //**成員變量**
TestObject *testObject; //**成員變量**
}
成員變量與上面的內存管理是一致的,只是在不同的函數里要保持引用計數加減的平衡所以要你要每次分配的時候檢查是否上次已經分配了。是否還能調用什么時候用屬性?
- 把成員做為public.(公開對象)
- outlet 一般聲明為屬性( 這個內存于系統控制,但我們還是應該做一樣操作,后面會講)
- 如果很多函數都需要改變這個對象 ,或這個函數會觸發很多次,建議使用屬性
我們看看屬性函數展開后是什么樣子:
**
**// assign
**
-(void)setTestObject :(id)newValue{
testObject= newValue;
}
**// retain
**
-(void)setTestObject :(id)newValue{
if (testObject!= newValue) {
[testObject release];
testObject= [newValue retain];
}
}
**// copy
**
-(void)setTestObject :(id)newValue{
if (testObject != newValue) {
[testObject release];
testObject = [newValue copy];
}
}
asssign 相于于指針賦值,不對引用計數進行操作,注意原對象不用了,一定要把這個設置為nil
retain 相當于對原對象的引用計數加1
copy 不對原對象的引用計數改變,生成一個新對象引用計數為1
注意: self.testObject 左值調用的是set TestObject 方法. 右值為get方法,get 方法比較簡單不用說了而 真接testObject 使用的是成員變量self.testObject = [[testObject alloc] init];
// 錯 reatin 兩次
(就是說,對于self..testObject 已經是創建對象并將引入技術+1,如果后面繼續用 [testObject alloc] init]會造成引入計數繼續+1,所以是2次 )testObject = [NSArray objectbyindex:0];
//錯 不安全,沒有retain 后面release會出錯
(這里沒有用 self. 說明 不會調用 set方法,所以引入計數仍然為 0 ,如果后面 進行release操作, 則會造成 內存訪問失敗)如果testObject已有值也會memory leak 內存泄露
自動管理對象iOS 提供了很多static(+) 創建對象的類方法,這些方面是靜態的,可以直接用類名調用如:
NSString *testString = [NSString stringWithFormat:@"test" ];
testString 是自動管理的對象,你不用relese 他,他有一個很大的 retain count, release后數字不變。
例外有一些通過alloc 生成的對象相同是自動管理的如:
NSString *testString = [[NSString alloc] initWithString:@"test1"];
retain count 同樣是很大的數,沒辦法release
但為了代碼對應,還是應該加上[ testString release]; //MRC 中需要不然xcode的Analyze 會認識內存leak, 但Instruments leak 工具檢測是沒有的
第四點(strong 和weak)
iOS 5 中對屬性的設置新增了strong 和weak關鍵字來修飾屬性(iOS 5 之前不支持ARC)
strong 用來修飾強引用的屬性;
@property (strong) SomeClass * aObject;
對應原來的
@property (retain) SomeClass * aObject;
和 @property (copy) SomeClass * aObject;
weak 用來修飾弱引用的屬性
@property (weak) SomeClass * aObject;
對應原來的@property (assign) SomeClass * aObject;
第五點(iOS內存nil與release的區別)
nil和release的作用:nil就是把一個對象的指針置為空,只是切斷了指針與內存中對象的聯系;
而release才是真正通知內存釋放這個對象,但是在iOS中其實也不會立馬釋放內存,而是將內存計數器剪去1,直到計數器變為0,才會釋放掉內存,
所以release的目的是為了釋放內存,而self.object = nil,是清空指針。
所以nil并沒有釋放內存,只有release才回真正釋放內存。
二者使用的先后順序:如果沒有release就直接nil,那么雖然不會出錯,卻等于自己制造內存泄漏了,因為nil之后release就已經不起作用了。相反,如果在使用接口對象時只僅僅release沒有設置self.myOutlet = nil,那么程序可能也不會報錯,但卻會十分不穩定、不健壯,很容易發生崩潰現象。因為一個接口對象在release之后,給它所分配等內存就已經被釋放了,如果釋放之后系統再用到這個對象,那么程序就會crash。如果釋放之后把它的指針置為空,則即便后面的程序用到該對象,也不會崩潰。
strong類似于retain
weak類似于assign
第六點(copy 和 retain 的區別)
copy: 建立一個索引計數為1的對象,然后釋放舊對象
retain: 釋放舊的對象,將舊對象的值賦予輸入對象,再提高輸入對象的索引計數為1那上面的是什么該死的意思呢?
Copy其實是建立了一個相同的對象,相當于是備份,而retain不是:比如一個NSString對象,地址為0×1111,內容為@”STR”Copy到另外一個NSString之后,地址為0×2222,內容相同,新的對象retain為1,舊有對象沒有變化retain到另外一個NSString之后,地址相同(建立一個指針,指針拷貝),內容當然相同,這個對象的retain值+1也就是說,retain是指針拷貝,copy是內容拷貝。哇,比想象的簡單多了…
第七點(對autorelease的誤解)autorelease其實是“延后釋放”
A Cocoa的內存管理分為 索引計數法(Reference Counting/ Retain Count)和 垃圾收集法(Garbage Collection)。
而iPhone上目前只支持前者,所以autorelease就成為很多人的“捷徑”。但是!autorelease其實并不是“自動釋放”,不像垃圾收集法,對對象之間的關系偵測后發現垃圾-刪除。
但是autorelease其實是“延后釋放”,在一個運行周期后被標記為autorelease會被釋放掉。切記小心使用autorelease,理解autorelease,防止在你還需要該對象的時候已經被系統釋放掉了。
第八點(其它)
NSArray對象會retain(retain值加一+1)任何數組中的對象。當NSArray被卸載(dealloc)的時候,所有數組中的對象會被執行一次釋放(retain值減一)。
不僅僅是NSArray,任何收集類(Collection Classes)都執行類似操作。例如NSDictionary,甚至UINavigationController。Alloc/init建立的對象,索引計數為1。無需將其再次retain。
為什么不能直接調用dealloc而是release dealloc不等于C中的free,dealloc并不將內存釋放,也不會將索引計數(Reference counting)降低。
于是直接調用dealloc反而無法釋放內存。
只是當當前情景下,如果創建的所有對象、屬性、成員變量等引入技術均為0 時,不會造成內存泄露,就會調用 dealloc 方法。
所以可以用以下方法 檢測 iOS 頁面是否出現內存泄露的請款
在Objective-C中,索引計數是起決定性作用的。