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