篇三上說iOS ARC(Automatic Reference Counting)管理的是堆上的OC對象。
1 開發(fā)人員對OC對象管理的四條規(guī)則
- 自己生成的對象搞动,自己持有
- 非自己生成的對象,自己也能持有
- 不需要自己持有的對象時(shí)釋放
- 非自己持有的對象不能釋放
上面的四條規(guī)則能夠幫助開發(fā)人員完成對OC對象的聲明周期的管理拒课,而ARC的工作原理也是模仿這一方式僧凰,通過引用計(jì)數(shù)。它會在合適的地方給我添加對OC對象的引用和釋放的代碼词裤。當(dāng)一個(gè)OC對象被引用時(shí),那么這個(gè)OC對象的引用數(shù)加1鳖宾;當(dāng)一個(gè)OC對象失去引用時(shí)吼砂,那么這個(gè)OC對象的數(shù)減1。當(dāng)OC對象引用數(shù)為0的時(shí)候就會被系統(tǒng)回收鼎文,回收的時(shí)機(jī)在下一個(gè)RunLoop循環(huán)開始渔肩,回收上一個(gè)循環(huán)的OC對象。
2 在內(nèi)存中管理對象的操作方法
Objective-c 方法名 | 功能 |
---|---|
alloc/new/copy/mutableCopy 方法 | 生成并持有對象拇惋,即使用這些方法生成對象周偎,那么對這個(gè)對象的引用至少為1 |
retain 方法 | 持有對象抹剩,即使這個(gè)對象的引用數(shù)加1 |
release 方法 | 釋放對象,即使這個(gè)對象的引用數(shù)減1蓉坎,當(dāng)這個(gè)引用數(shù)為0的時(shí)候吧兔,系統(tǒng)自動(dòng)調(diào)用dealloc方法釋放對象所占用的空間 |
dealloc 方法 | 釋放對象,這個(gè)釋放必須是對象的引用數(shù)為零才能釋放 |
2.1 Tips:copy和mutableCopy的區(qū)別
方法 | 功能 | 協(xié)議 | 實(shí)現(xiàn) | 可變對象調(diào)用 | 不可變對象調(diào)用 |
---|---|---|---|---|---|
copy | 生成并持有不可變對象的副本 | NSCopying | copyWithZone: | 深復(fù)制 | 淺復(fù)制 |
mutableCopy | 生成并持有可變對象的副本 | NSMutableCopying | mutableCopyWithZone: | 深復(fù)制 | 深復(fù)制 |
由 上表可以知道袍嬉,使用copy方法復(fù)制的并不都是淺復(fù)制,為了避免出錯(cuò)灶平,如果要對一個(gè)對象進(jìn)行深復(fù)制伺通,則使用mutableCopy方法。
2.2 autorelease/__autoreleasing 關(guān)鍵字
autorelease
是在MRC
環(huán)境下的關(guān)鍵字逢享,而__autoreleasing
是ARC
環(huán)境下的關(guān)鍵字罐监,它們是等價(jià)的,只是看編譯環(huán)境是不是支持ARC
瞒爬。該關(guān)鍵字:它可以取得對象弓柱,但是不持有對象,利用這個(gè)特性我們可以在下面的方法中返回一個(gè)不會被持有的對象和降低內(nèi)存峰值侧但。
-(id) object{
id obj = [NSObject new];
[obj autorelease]; // obj不持有[NSObject new] 生成的對象
return obj; // 等價(jià)于返回一個(gè)引用
}
問題1:為什么autorelease它可以取得對象矢空,但是不持有對象?
原因是: 調(diào)用這個(gè)方法禀横,它可以把這個(gè)對象加入到自動(dòng)釋放池中屁药,在清理自動(dòng)釋放池的時(shí)候,系統(tǒng)自動(dòng)會為釋放池中的對象調(diào)用[obj release];
柏锄。
問題2:如何降低內(nèi)存峰值酿箭?
在回答這個(gè)問題之前,先介紹內(nèi)存峰值和對比在MRC
和ARC
的自動(dòng)釋放池趾娃。
內(nèi)存峰值:它是指在某一段較小的時(shí)間占用的內(nèi)存高于它鄰近時(shí)間的內(nèi)存占有量缭嫡。
在MRC環(huán)境下的創(chuàng)建自動(dòng)釋放池,分為三步驟:
第一步:生成并持有釋放池NSAutoreleasePool對象;
第二步:調(diào)用對象的autorelease方法抬闷,使對象加入連接池中;
第三步:釋放NSAutoreleasePool對象妇蛀,然后系統(tǒng)會對這個(gè)池中的每一個(gè)對象調(diào)用release方法;
//MRC環(huán)境下:
//第一步:生成并持有釋放池NSAutoreleasePool對象;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 在堆內(nèi)存中生成一個(gè)對象
id obj = [[NSObject alloc] init];
// 第二步:調(diào)用autorelease方法,使對象加入連接池中;
[obj autorelease];
//第三步:釋放NSAutoreleasePool對象;
[pool drain]; //向池中的所有對象調(diào)用release方法饶氏,此處由系統(tǒng)調(diào)用[obj release];
//obi已經(jīng)釋放讥耗,再次調(diào)用會崩潰
NSLog(@"%@", obj);
由上例代碼可知,對自動(dòng)釋放池的生成和釋放的代碼是固定不變的疹启,所以在ARC
環(huán)境下古程,它把自動(dòng)釋放池的創(chuàng)建和釋放交給編譯器了,直接使用@autoreleasepool
塊即可喊崖。例如上例可以改成:
@autoreleasepool{// 這里生成NSAutoreleasePool對象
// 在堆內(nèi)存中生成一個(gè)對象
id obj = [[NSObject alloc] init];
} // 在這里自動(dòng)調(diào)用[pool drain];
接下來假設(shè)一個(gè)減少內(nèi)存峰值的一個(gè)應(yīng)用場景:通過一個(gè)for循環(huán)加載大量文件挣磨,并用一個(gè)字符串來接收雇逞,如下所示:
// 此處只是模擬場景,
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
/*處理字符串茁裙,創(chuàng)建并自動(dòng)釋放更多對象塘砸。 */
}
上例代碼,在for循環(huán)內(nèi)會占用大量的內(nèi)存晤锥,這時(shí)就會產(chǎn)生內(nèi)存峰值掉蔬,如果我們使用自動(dòng)釋放池就可以降低內(nèi)存峰值,如下所示矾瘾。
// 此處只是模擬場景女轿,
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
/*處理字符串,創(chuàng)建并自動(dòng)釋放更多對象壕翩。 */
}// 執(zhí)行到這里蛉迹,會釋放池內(nèi)的所有局部變量,這里會把fileContents釋放掉放妈。
}
3 自動(dòng)釋放池(NSAutoreleasePool)
自動(dòng)釋放池還有一個(gè)特性就是可以嵌套使用北救,如下面所示。
@autoreleasepool {
// . . .
@autoreleasepool {
// . . .
}
// . . .
}
注意:雖然自動(dòng)釋放池芜抒,能夠幫助我們清理對象珍策,但是不建議大量的使用自動(dòng)釋放池,原因有二:一是雖然自動(dòng)釋放池雖然占用較小挽绩,但是大量的話也會占有一定的內(nèi)存膛壹;二是頻繁的清理對象,會消耗CPU的性能和耗電唉堪。
3.1 RunLoop和NSAutoreleasePool的關(guān)系
每一個(gè)RunLoop都會創(chuàng)建一個(gè)Autorelease Pool對象模聋,系統(tǒng)會為同一個(gè)RunLoop 的所有Autorelease Pool對維護(hù)一個(gè)棧,每個(gè)新創(chuàng)建的Autorelease Pool對象會壓入棧頂唠亚,待RunLoop結(jié)束時(shí)链方,彈出棧頂,同時(shí)清理Autorelease Pool的所有對象灶搜。
3.2 線程和NSAutoreleasePool的關(guān)系
每一個(gè)線程都會有自己的Autorelease Pool 和一個(gè)RunLoop祟蚀,但是非主線程的RunLoop,默認(rèn)不回開啟割卖,需要手動(dòng)開啟前酿。當(dāng)線程終止的時(shí)候,系統(tǒng)會自動(dòng)清理該線程的Autorelease Pool 對象鹏溯。所以在程序開始運(yùn)行的時(shí)候罢维,主線程創(chuàng)建后,RunLoop和Autorelease Pool也會創(chuàng)建丙挽,并且RunLoop啟動(dòng)肺孵。
總結(jié)
在現(xiàn)在的iOS的開發(fā)幾乎都是處于ARC環(huán)境下匀借,ARC的工作原理也是非常簡單的,就是監(jiān)控一個(gè)對象的引用數(shù)平窘,如果這個(gè)對象的引用數(shù)為0吓肋,系統(tǒng)就會回收這個(gè)對象。這樣就幫助開發(fā)人員從MRC編程釋放出來瑰艘。本文主要介紹了對一個(gè)對象從生成到釋放和在ARC環(huán)境下是鬼,@autorelease的用法和它的背后原理。