长路漫漫,唯剑作伴--Automatic Reference Counting
一、引用計(jì)數(shù)
在OC中,對(duì)象什么時(shí)候會(huì)被釋放?
- 答案是當(dāng)對(duì)象沒有被任何變量引用(也可以說(shuō)是沒有指針指向該對(duì)象)的時(shí)候,就會(huì)被釋放。
怎么知道對(duì)象已經(jīng)沒有被引用了呢?
-
OC采用引用計(jì)數(shù)(reference counting)的技術(shù)來(lái)進(jìn)行管理:
-
每個(gè)對(duì)象都有一個(gè)關(guān)聯(lián)的整數(shù),稱為引用計(jì)數(shù)器
-
當(dāng)代碼需要使用該對(duì)象時(shí),則將對(duì)象的引用計(jì)數(shù)加1
-
當(dāng)代碼結(jié)束使用該對(duì)象時(shí),則將對(duì)象的引用計(jì)數(shù)減1
-
當(dāng)引用計(jì)數(shù)的值變?yōu)?時(shí),表示對(duì)象沒有被任何代碼使用,此時(shí)對(duì)象將被釋放。
-
-
內(nèi)存管理的思考方式:
-
自己創(chuàng)建的對(duì)象自己管理。
-
不是自己創(chuàng)建的對(duì)象,自己也能持有。
-
不再需要自己持有的對(duì)象時(shí),釋放。
-
不是自己持有的對(duì)象,不能釋放。
-
總結(jié)一句話就是:誰(shuí)創(chuàng)建,誰(shuí)釋放,誰(shuí)持有,誰(shuí)管理。
-
-
與之對(duì)應(yīng)的消息發(fā)送方法如下:
-
增加引用計(jì)數(shù):alloc、new、copy、mutableCopy(此為生成),retain(此為持有對(duì)象)。
-
減少引用計(jì)數(shù):release(此為釋放對(duì)象)。
-
釋放dealloc(此為廢棄對(duì)象)
-
-
下面通過(guò)一個(gè)簡(jiǎn)單的例子說(shuō)明:
-
新建Dog類,重寫其創(chuàng)建和銷毀的方法:
@implementation Dog- (instancetype)init {if (self = [super init]) {NSLog(@"小狗被派出去啦!初始引用計(jì)數(shù)為 %ld",self.retainCount);}return self;}- (void)dealloc {NSLog(@"小狗回到寵物中心");[super dealloc];} @end -
在main方法中創(chuàng)建dog對(duì)象,給dog發(fā)送消息:
//模擬:寵物中心派出小狗 Dog * dog = [[Dog alloc]init]; //模擬:xiaoming需要和小狗玩耍,需要將其引用計(jì)數(shù)加1 [dog retain]; NSLog(@"小狗的引用計(jì)數(shù)為 %ld",dog.retainCount); //模擬:xiaoming不和小狗玩耍了,需要將其引用計(jì)數(shù)減1 [dog release]; NSLog(@"小狗的引用計(jì)數(shù)為 %ld",dog.retainCount); //沒人需要和小狗玩耍了,將其引用計(jì)數(shù)減1 [dog release]; //將指針置nil,否則變?yōu)橐爸羔?/span> dog = nil; -
輸出結(jié)果為:
[34691:7638855] 初始引用計(jì)數(shù)為 1 [34691:7638855] 小狗的引用計(jì)數(shù)為 2 [34691:7638855] 小狗的引用計(jì)數(shù)為 1 [34691:7638855] 銷毀Dog -
可以看到,引用計(jì)數(shù)幫助寵物中心很好的標(biāo)記了小狗的使用狀態(tài),在完成任務(wù)的時(shí)候及時(shí)收回到寵物中心。
-
-
思考幾個(gè)問(wèn)題:
-
NSString引用計(jì)數(shù)問(wèn)題:
NSString * str = @"hello guys"; NSLog(@"%ld", str.retainCount); // 會(huì)發(fā)現(xiàn)引用計(jì)數(shù)為-1,這可以理解為NSString實(shí)際上是一個(gè)字符串常量,是沒有引用計(jì)數(shù)的(或者它的引用計(jì)數(shù)是一個(gè)很大的值(使用%lu可以打印查看),對(duì)它做引用計(jì)數(shù)操作沒實(shí)質(zhì)上的影響)。 -
賦值不會(huì)擁有某個(gè)對(duì)象:
NSString * name = dog.name; // 這里僅僅是指針賦值操作,并不會(huì)增加name的引用計(jì)數(shù),需要持有對(duì)象必須要發(fā)送retain消息。 -
dealloc:
-
由于釋放對(duì)象是會(huì)調(diào)用dealloc方法,因此重寫dealloc方法來(lái)查看對(duì)象釋放的情況,如果沒有調(diào)用則會(huì)造成內(nèi)存泄露。在上面的例子中我們通過(guò)重寫dealloc讓小狗被釋放的時(shí)候打印日志來(lái)告訴我們已經(jīng)完成釋放。
-
-
在上面例子中,如果我們?cè)黾舆@樣一個(gè)操作:
//沒人需要和小狗玩耍了,將其引用計(jì)數(shù)減1 [dog release]; NSLog(@"%ld",dog.retainCount); // 會(huì)發(fā)現(xiàn)獲取到的引用計(jì)數(shù)為1,為什么不是0呢? // 這是因?yàn)閷?duì)引用計(jì)數(shù)為1的對(duì)象release時(shí),系統(tǒng)知道該對(duì)象將被回收,就不會(huì)再對(duì)該對(duì)象的引用計(jì)數(shù)進(jìn)行減1操作,這樣可以增加對(duì)象回收的效率。
-
?二、自動(dòng)釋放池
思考
-
現(xiàn)在已經(jīng)明確了,當(dāng)不再使用一個(gè)對(duì)象時(shí)應(yīng)該將其釋放,但是在某些情況下,我們很難理清一個(gè)對(duì)象什么時(shí)候不再使用(比如xiaoming和小狗玩耍結(jié)束的時(shí)間不確定),這可怎么辦?
-
ObjC提供autorelease方法來(lái)解決這個(gè)問(wèn)題,當(dāng)給一個(gè)對(duì)象發(fā)送autorelease消息時(shí),方法會(huì)在未來(lái)某個(gè)時(shí)間給這個(gè)對(duì)象發(fā)送release消息將其釋放,在這個(gè)時(shí)間段內(nèi),對(duì)象還是可以使用的。
那autorelease的原理是什么呢?
-
原理就是對(duì)象接收到autorelease消息時(shí),它會(huì)被添加到了當(dāng)前的自動(dòng)釋放池中,當(dāng)自動(dòng)釋放池被銷毀時(shí),會(huì)給池里所有的對(duì)象發(fā)送release消息。
創(chuàng)建
-
方法一:使用NSAutoreleasePool來(lái)創(chuàng)建
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init]; //這里寫代碼 [pool release]; -
方法二:使用@autoreleasepool創(chuàng)建
@autoreleasepool {//這里寫代碼 } -
自動(dòng)釋放池創(chuàng)建后,就會(huì)成為活動(dòng)的池子,釋放池子后,池子將釋放其所包含的所有對(duì)象。
-
以上兩種方法推薦第一種,因?yàn)閷?nèi)存交給ObjC管理更高效。
-
自動(dòng)釋放池什么時(shí)候創(chuàng)建?
-
app使用過(guò)程中,會(huì)定期自動(dòng)生成和銷毀自動(dòng)釋放池,一般是在程序事件處理之前創(chuàng)建,當(dāng)然我們也可以自行創(chuàng)建自動(dòng)釋放池,來(lái)達(dá)到我們一些特定的目的。
-
-
自動(dòng)釋放池什么時(shí)候銷毀?
-
自動(dòng)釋放池的銷毀時(shí)間是確定的,一般是在程序事件處理之后釋放,或者由我們自己手動(dòng)釋放。
-
下面舉例說(shuō)明自動(dòng)釋放池的工作流程:
-
代碼
//創(chuàng)建一個(gè)自動(dòng)釋放池 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; //模擬:寵物中心派出小狗 Dog * dog = [[Dog alloc]init]; //模擬:xiaoming需要和小狗玩耍,需要將其引用計(jì)數(shù)加1 [dog retain]; NSLog(@"小狗的引用計(jì)數(shù)為 %ld",dog.retainCount); //模擬:xiaohong需要和小狗玩耍,需要將其引用計(jì)數(shù)加1 [dog retain]; NSLog(@"小狗的引用計(jì)數(shù)為 %ld",dog.retainCount); //模擬:xiaoming確定不想和小狗玩耍了,需要將其引用計(jì)數(shù)減1 [dog release]; NSLog(@"小狗的引用計(jì)數(shù)為 %ld",dog.retainCount); //模擬:xiaohong不確定何時(shí)不想和小狗玩耍了,將其設(shè)置為自動(dòng)釋放 [dog autorelease]; NSLog(@"小狗的引用計(jì)數(shù)為 %ld",dog.retainCount); //沒人需要和小狗玩耍了,將其引用計(jì)數(shù)減1 [dog release]; NSLog(@"釋放池子"); [pool release]; //創(chuàng)建一個(gè)自動(dòng)釋放池 @autoreleasepool {//模擬:寵物中心派出小狗Dog * dog = [[Dog alloc]init];//模擬:xiaoming需要和小狗玩耍,需要將其引用計(jì)數(shù)加1 [dog retain];NSLog(@"小狗的引用計(jì)數(shù)為 %ld",dog.retainCount);//模擬:xiaohong需要和小狗玩耍,需要將其引用計(jì)數(shù)加1 [dog retain];NSLog(@"小狗的引用計(jì)數(shù)為 %ld",dog.retainCount);//模擬:xiaoming確定不想和小狗玩耍了,需要將其引用計(jì)數(shù)減1 [dog release];NSLog(@"小狗的引用計(jì)數(shù)為 %ld",dog.retainCount);//模擬:xiaohong不確定何時(shí)不想和小狗玩耍了,將其設(shè)置為自動(dòng)釋放 [dog autorelease];NSLog(@"小狗的引用計(jì)數(shù)為 %ld",dog.retainCount);//沒人需要和小狗玩耍了,將其引用計(jì)數(shù)減1 [dog release];NSLog(@"釋放池子"); } -
結(jié)果
[34819:7801589] 初始引用計(jì)數(shù)為 1 [34819:7801589] 小狗的引用計(jì)數(shù)為 2 [34819:7801589] 小狗的引用計(jì)數(shù)為 3 [34819:7801589] 小狗的引用計(jì)數(shù)為 2 [34819:7801589] 小狗的引用計(jì)數(shù)為 2 [34819:7801589] 釋放池子 [34819:7801589] 銷毀Dog // 可以看到,當(dāng)池子釋放后,dog對(duì)象才被釋放,因此在池子釋放之前,xiaohong都可以盡情地和小狗玩耍。
使用自動(dòng)釋放池需要注意:
-
自動(dòng)釋放池實(shí)質(zhì)上只是在釋放的時(shí)候給池中所有對(duì)象對(duì)象發(fā)送release消息,不保證對(duì)象一定會(huì)銷毀,如果自動(dòng)釋放池向?qū)ο蟀l(fā)送release消息后對(duì)象的引用計(jì)數(shù)仍大于1,對(duì)象就無(wú)法銷毀。
-
自動(dòng)釋放池中的對(duì)象會(huì)集中同一時(shí)間釋放,如果操作需要生成的對(duì)象較多占用內(nèi)存空間大,可以使用多個(gè)釋放池來(lái)進(jìn)行優(yōu)化。比如在一個(gè)循環(huán)中需要?jiǎng)?chuàng)建大量的臨時(shí)變量,可以創(chuàng)建內(nèi)部的池子來(lái)降低內(nèi)存占用峰值。
-
autorelease不會(huì)改變對(duì)象的引用計(jì)數(shù)。
自動(dòng)釋放池的常見問(wèn)題
-
在管理對(duì)象釋放的問(wèn)題上,自動(dòng)幫助我們釋放池節(jié)省了大量的時(shí)間,但是有時(shí)候它卻未必會(huì)達(dá)到我們期望的效果,比如在一個(gè)循環(huán)事件中,如果循環(huán)次數(shù)較大或者事件處理占用內(nèi)存較大,就會(huì)導(dǎo)致內(nèi)存占用不斷增長(zhǎng),可能會(huì)導(dǎo)致不希望看到的后果。
-
示例代碼:
for (int i = 0; i < 100000; i ++) {NSString * log = [NSString stringWithFormat:@"%d", i];NSLog(@"%@", log); } -
前面講過(guò),自動(dòng)釋放池的釋放時(shí)間是確定的,這個(gè)例子中自動(dòng)釋放池會(huì)在循環(huán)事件結(jié)束時(shí)釋放,那問(wèn)題來(lái)了:在這個(gè)十萬(wàn)次的循環(huán)中,每次都會(huì)生成一個(gè)字符串并打印,這些字符串對(duì)象都放在池子中并直到循環(huán)結(jié)束才會(huì)釋放,因此在循環(huán)期間內(nèi)存不增長(zhǎng)。
-
這類問(wèn)題的解決方案是在循環(huán)中創(chuàng)建新的自動(dòng)釋放池,多少個(gè)循環(huán)釋放一次由我們自行決定。
for (int i = 0; i < 100000; i ++) {@autoreleasepool {NSString * log = [NSString stringWithFormat:@"%d", i];NSLog(@"%@", log);} }
被autorelease處理過(guò)的對(duì)象的釋放時(shí)機(jī)
-
autorelease并不是根據(jù)作用域來(lái)決定釋放時(shí)機(jī)的,而是Runloop。當(dāng)一個(gè)runloop結(jié)束時(shí)系統(tǒng)才會(huì)一次性清理掉被autorelease處理過(guò)的對(duì)象,其實(shí)本質(zhì)上說(shuō)是在本次runloop迭代結(jié)束時(shí)清理掉被本次迭代期間被放到autorelease pool中的對(duì)象的。至于何時(shí)runloop結(jié)束并沒有固定的duration!
?
三、ARC
簡(jiǎn)介
-
ARC,自動(dòng)引用計(jì)數(shù),是指iOS的內(nèi)存管理使用引用計(jì)數(shù)的技術(shù)。
-
在OC中采用Automatic Reference Counting的機(jī)制,讓編譯器進(jìn)行內(nèi)存管理。在新一代的Apple LLVM編譯器中設(shè)置ARC為有效狀態(tài),就不用再次鍵入retain、release代碼,這在降低程序崩潰、內(nèi)存泄漏等風(fēng)險(xiǎn)的同時(shí),很大程度上減少了開發(fā)程序的工作量。編譯器完全清楚目標(biāo)對(duì)象,并能立刻釋放那些不再被使用的對(duì)象(有待斟酌)。如此一來(lái),應(yīng)用程序?qū)⒕哂锌深A(yù)測(cè)性,且運(yùn)行流暢,速度也將大幅提升。(摘自蘋果官方文檔說(shuō)明)
ARC修飾符
-
__strong:強(qiáng)引用,持有所指向?qū)ο蟮乃袡?quán),無(wú)修飾符情況下的默認(rèn)值。如需強(qiáng)制釋放,可置nil。
-
比如我們常用的定時(shí)器:NSTimer?*?timer?=?[NSTimer?timerWith...];
-
相當(dāng)于NSTimer?*?__strong?timer?=?[NSTimer?timerWith...];
-
當(dāng)不需要使用時(shí),強(qiáng)制銷毀定時(shí)器:[timer?invalidate];timer?=?nil;
-
-
__weak:弱引用,不持有所指向?qū)ο蟮乃袡?quán),引用指向的對(duì)象內(nèi)存被回收之后,引用本身會(huì)置nil,避免野指針。
-
__weak?__typeof(self)?weakSelf?=?self;
-
-
__autoreleasing:自動(dòng)釋放對(duì)象的引用,一般用于傳遞參數(shù)
-
__unsafe_unretained:為兼容iOS5以下版本的產(chǎn)物,可以理解成MRC下的weak,現(xiàn)在基本用不到,這里不作描述
-
使用__autoreleasing可能會(huì)遇到哪些問(wèn)題?
-
使用修飾符的正確姿勢(shì)
NSString * __weak str = @"hehe"; // 正確! __weak NSString *str = @"hehe"; // 錯(cuò)誤!// 我相信很多人都和我一樣,從開始用ARC就一直用上面那種錯(cuò)誤的寫法。 // 那這里就有疑問(wèn)了,既然文檔說(shuō)是錯(cuò)誤的,為啥編譯器不報(bào)錯(cuò)呢?好吧,是蘋果考慮到很多人會(huì)用錯(cuò),所以在編譯器這邊貼心地幫我們忽略并處理掉了這個(gè)錯(cuò)誤:) // 雖然不報(bào)錯(cuò),但是我們還是應(yīng)該按照正確的方式去使用這些修飾符 // 如果你以前也常常用錯(cuò)誤的寫法,那看到這里記得以后不要這么寫了,哪天編譯器怒了,再不支持錯(cuò)誤的寫法,就要郁悶了。 // (參見蘋果官方文檔)
?
?
?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/zhuyiios/p/6710190.html
總結(jié)
以上是生活随笔為你收集整理的长路漫漫,唯剑作伴--Automatic Reference Counting的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Hibernate二级缓存配置
- 下一篇: 键盘事件相关