iOS內(nèi)存管理機(jī)制的原理是引用計數(shù),引用計數(shù)簡單來說就是統(tǒng)計一塊內(nèi)存的所有權(quán),當(dāng)這塊內(nèi)存被創(chuàng)建出來的時候,它的引用計數(shù)從0增加到1,表示有一個對象或指針持有這塊內(nèi)存,擁有這塊內(nèi)存的所有權(quán),如果這時候有另外一個對象或指針指向這塊內(nèi)存,那么為了表示這個后來的對象或指針對這塊內(nèi)存的所有權(quán),引用計數(shù)加1變?yōu)?,之后若有一個對象或指針不再指向這塊內(nèi)存時,引用計數(shù)減1,表示這個對象或指針不再擁有這塊內(nèi)存的所有權(quán),當(dāng)一塊內(nèi)存的引用計數(shù)變?yōu)?,表示沒有任何對象或指針持有這塊內(nèi)存,系統(tǒng)便會立刻釋放掉這塊內(nèi)存。
其中在開發(fā)時引用計數(shù)又分為ARC(自動內(nèi)存管理)和MRC(手動內(nèi)存管理)。ARC的本質(zhì)其實就是MRC,只不過是系統(tǒng)幫助開發(fā)者管理已創(chuàng)建的對象或內(nèi)存空間,自動在系統(tǒng)認(rèn)為合適的時間和地點(diǎn)釋放掉已經(jīng)失去作用的內(nèi)存空間,原理是一樣的。雖然ARC操作起來很方便,不但減少了代碼量,而且降低了內(nèi)存出錯的概率,但因為ARC不一定會及時釋放,所以程序有時候可能會占用內(nèi)存較大。而MRC若做得好,通過手動管理,及時釋放掉不需要的內(nèi)存空間,便可保證程序長時間運(yùn)行在良好狀態(tài)上。
在MRC中會引起引用計數(shù)變化的關(guān)鍵字有:alloc,retain,copy,release,autorelease。(strong關(guān)鍵字只用于ARC,作用等同于retain)
alloc:當(dāng)一個類的對象創(chuàng)建,需要開辟內(nèi)存空間的時候,會使用alloc,alloc是一個類方法,只能用類調(diào)用,它的作用是開辟一塊新的內(nèi)存空間,并使這塊內(nèi)存的引用計數(shù)從0增加到1,注意,是新的內(nèi)存空間,每次用類alloc出來的都是一塊新的內(nèi)存空間,與上一次alloc出來的內(nèi)存空間沒有必然聯(lián)系,而且上一次alloc出來的內(nèi)存空間仍然存在,不會被釋放。
retain:retain是一個實例方法,只能由對象調(diào)用,它的作用是使這個對象的內(nèi)存空間的引用計數(shù)加1,并不會新開辟一塊內(nèi)存空間,通常于賦值是調(diào)用,如:
對象2=[對象1 retain];表示對象2同樣擁有這塊內(nèi)存的所有權(quán)。若只是簡單地賦值,如:對象2=對象1;那么當(dāng)對象1的內(nèi)存空間被釋放的時候,對象2便會成為野指針,再對對象2進(jìn)行操作便會造成內(nèi)存錯誤。
copy:copy同樣是一個實例方法,只能由對象調(diào)用,返回一個新的對象,它的作用是復(fù)制一個對象到一塊新的內(nèi)存空間上,舊內(nèi)存空間的引用計數(shù)不會變化,新的內(nèi)存空間的引用計數(shù)從0增加到1,也就是說,雖然內(nèi)容一樣,但實質(zhì)上是兩塊內(nèi)存,相當(dāng)于克隆,一個變成兩個。其中copy又分為淺拷貝、深拷貝和真正的深拷貝,淺拷貝只是拷貝地址與retain等同;深拷貝是拷貝內(nèi)容,會新開辟新內(nèi)存,與retain不一樣;真正的深拷貝是對于容器類來說的,如數(shù)組類、字典類和集合類(包括可變和不可變),假設(shè)有一個數(shù)組類對象,普通的深拷貝會開辟一塊新內(nèi)存存放這個對象,但這個數(shù)組對象里面的各個元素的地址卻沒有改變也就是說數(shù)組元素只是進(jìn)行了retain或者淺拷貝而已,并沒有創(chuàng)建新的內(nèi)存空間,而真正的深拷貝,不但數(shù)組對象本身進(jìn)行了深拷貝,連數(shù)組元素都進(jìn)行了深拷貝,即為各個數(shù)組元素開辟了新的內(nèi)存空間。
release:release是一個實例方法,同樣只能由對象調(diào)用,它的作用是使對象的內(nèi)存空間的引用計數(shù)減1,若引用計數(shù)變?yōu)?則系統(tǒng)會立刻釋放掉這塊內(nèi)存。如果引用計數(shù)為0的基礎(chǔ)上再調(diào)用release,便會造成過度釋放,使內(nèi)存崩潰;
autorelease:autorelease是一個實例方法,同樣只能由對象調(diào)用,它的作用于release類似,但不是立刻減1,相當(dāng)于一個延遲的release,通常用于方法返回值的釋放,如便利構(gòu)造器。autorelease會在程序走出自動釋放池時執(zhí)行,通常系統(tǒng)會自動生成自動釋放池(即使是MRC下),也可以自己設(shè)定自動釋放池,如:
@autoreleasepool{
obj= [[NSObject alloc]init];
[obj autorelease];
}
當(dāng)程序走出“}”時obj的引用計數(shù)就會減1.
除了以上所述的關(guān)鍵字,還有一些方法會引起引用計數(shù)的變化,如UI中父視圖添加、移除子視圖,導(dǎo)航控制器或視圖控制器推出新的視圖控制器以及返回,容器類(數(shù)組、字典和集合)添加和移除元素。
當(dāng)子視圖添加到父視圖上時,子視圖的引用計數(shù)加1,移除時引用計數(shù)減1,若父視圖引用計數(shù)變?yōu)?內(nèi)存被釋放,其所有的子視圖都會被release一次,即引用計數(shù)減1,原則上只有這三種情況子視圖的引用計數(shù)會發(fā)生變化,其他如父視圖引用計數(shù)的加減都不會影響到子視圖。
容器類的情況與視圖類似,添加元素,該元素引用計數(shù)加1,移除元素,該元素引用計數(shù)減1,容器引用計數(shù)變?yōu)?所占用內(nèi)存被釋放,容器所有元素release,引用計數(shù)減1,其他情況下容器本身的引用計數(shù)變化不會影響到容器內(nèi)元素的引用計數(shù)變化。
導(dǎo)航控制器或視圖控制器推出新的視圖控制器會使被推出的視圖控制器的引用計數(shù)加1,該視圖控制器返回的時候引用計數(shù)減1,具體方法如下:
導(dǎo)航控制器推出視圖控制器調(diào)用方法:- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated;
返回時同樣用導(dǎo)航控制器調(diào)用方法:- (UIViewController *)popViewControllerAnimated:(BOOL)animated;
視圖控制器推出視圖控制器調(diào)用方法:- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^)(void))completion
返回時被推出的視圖控制器調(diào)用方法:- (void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^)(void))completion
應(yīng)注意:當(dāng)一個對象的引用計數(shù)變?yōu)?占用內(nèi)存被釋放時,會調(diào)用- (void)dealloc方法,所以如果在MRC下自定義類,必須在該方法里將該類中屬性關(guān)鍵字設(shè)置為retain或copy的屬性release一次,以免造成內(nèi)存泄露,重寫方法不要忘記在第一行添加[super dealloc]。