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