国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

Objective-C內存管理之引用計數

2019-11-14 20:07:52
字體:
來源:轉載
供稿:網友

  初學者在學習Objective-c的時候,很容易在內存管理這一部分陷入混亂狀態,很大一部分原因是沒有弄清楚引用計數的原理,搞不明白對象的引用數量,這樣就當然無法徹底釋放對象的內存了,蘋果官方文檔在內存管理這一部分說的非常簡單,只有三條準則:

  1. 當你使用new、alloc或copy方法創建一個對象時,該對象的保留指針為1,當不再使用該對象的時候,你應該想該對象發送一條release或autorelease消息,這樣,該對象在其壽命結束時將被銷毀。
  2. 當你通過其他方法獲得一個對象時,假設該對象的保留計數器為1,而且已經設置為自動釋放,那么你不需要執行任何操作來確保該對象被銷毀。如果你打算在一段時間內擁有該對象,則需要保留它并確保它在操作完成時釋放它。
  3. 如果你保留了某個對象,就需要(最終)釋放或自動釋放該對象。必須保持retain方法和release方法的使用次數相同。

  如果在寫代碼的時候遵守這些準則,可以避免內存泄露,但是如果僅靠對這些準則的“記憶”來寫代碼的話,恐怕自己心里都不會有底,一旦遇到問題分析問題的時候很難從根本上找到問題出現的原因,本文分享了自己在理解引用計數時的分析過程,結合相關圖形,希望能讓大家深刻理解對象引用計數的原理。

遇到了問題?分析然后測試

  當前對象的引用計數是多少呢?

     為什么要提出這個問題,因為很多人會搞混對象的指針數量與引用數量的關系,不理解這個問題就弄不明白對象的引用計數到底為多少,當然就無法正確釋放內存了。在說這個之前先簡單了解一下堆內存與棧內存的概念,

  1. 棧內存:由編譯器負責分配,存放局部環境中定義的基本變量的值,例如方法中的基本變量等,離開局部環境時會由編譯器自動釋放其內存空間。
  2. 堆內存: 一般由程序員通過new或alloc等來手動分配,使用完后也需要程序員手動釋放,若程序員不釋放,程序結束時可能由OS回收。注意它與數據結構中的堆是兩回事。

     變量名實際上是一個符號地址,在對程序編譯連接時由系統給每一個變量名分配一個內存地址。在程序中從變量中取值,實際上是通過變量名找到相應的內存地址,從其存儲單元中讀取數據。指針是一個特殊的變量,因為它存放的是一個變量的地址。如下圖所示:

 

  上面這個內存模型相信大家都知道,指針與對象存在一個間接(指向)的關系,因此當指針指向一個對象的時候,很多人就會覺得這個指針引用到了該對象,進而就認為當指針指向一個對象的時候,該對象的引用計數就會加1,這種理解是一種感性的理解。實際上對于一個對象來說,它是不知道指向它的指針有多少個的,它的釋放僅僅依靠的是引用計數,那么什么是引用計數呢?在objective-c中,大部分對象都繼承于NSObject,NSObject包含一個用來保存引用數量的字段retainCount,說白了該字段就是引用計數,NSObject類的部分定義如下:
- (id)retain;
- (oneway void)release;
- (id)autorelease;
- (NSUInteger)retainCount;
- (NSString *)description;

因此,為了便于理解,我們可以把NSObject簡化為如下模型:

 

  對象能否釋放就是判斷其引用次數是否為零,也就是判斷該對象的retainCount字段是否等于0,而指向該對象指針數量跟該對象retainCount字段的值并沒有關系,因此指針數量并不等于引用數量,當指針指向該對象的時候,僅僅是給該指針變量賦值了,并沒有修改對象的retainCount值,因此,指針指向一個對象的時候,該對象的引用計數是沒有改變的。

  以上面那段代碼為例,我們調用Test對象的new方法的時候,會自動將retainCount的值設置為1,當我們將test1賦值給test2的時候,只是一個指針賦值,并沒有修改對象的retainCount值,所以引用計數不變,依舊為1。

測試用例:

 1         Engine *engine1 = [Engine new]; 2         NSLog(@"通過new消息創建對象engine1:"); 3         //輸出engine1指針地址 4         NSLog(@"engine1 address is %p.",engine1); 5         //輸出engine1的retainCount 6         NSLog(@"engine1 retainCount is %lu",(unsigned long)[engine1 retainCount]); 7          8         Engine *engine2 = engine1; 9         NSLog(@"將指針engine1復制給指針engine2:");10         //輸出engine2指針地址11         NSLog(@"engine2 address is %p.",engine2);12         //輸出engine1的retainCount13         NSLog(@"engine1 retainCount is %lu",(unsigned long)[engine1 retainCount]);14         //輸出engine2的retainCount15         NSLog(@"engine2 retainCount is %lu",(unsigned long)[engine2 retainCount]);16         17         Engine *engine3 = [engine1 retain];18         NSLog(@"通過retain消息獲得對象engine3:");19         //輸出engine3指針地址20         NSLog(@"engine3 address is %p.",engine3);21         //輸出engine1的retainCount22         NSLog(@"engine1 retainCount is %lu",(unsigned long)[engine1 retainCount]);23         //輸出engine2的retainCount24         NSLog(@"engine2 retainCount is %lu",(unsigned long)[engine2 retainCount]);25         //輸出engine3的retainCount26         NSLog(@"engine3 retainCount is %lu",(unsigned long)[engine3 retainCount]);27         28         [engine2 release];29         NSLog(@"給對象engine2發送消息release");30         NSLog(@"engine2 address is %p.",engine2);31         NSLog(@"engine2 retainCount is %lu.",(unsigned long)[engine2 retainCount]);

輸出結果如下:

  從上面的輸出結果可以得出以下幾點結論:

  1. 和本文一開始分析得出的結果一樣,通過指針賦值并不能改變對象的引用計數。
  2. 不論是通過指針賦值還是通過retain獲得對象,它們都指向同一個內存地址,即:指向同一個對象
  3. 在對象的引用計數歸零之前,所有指向它的指針都是可用的。通過某個指針發送release消息僅僅是讓引用計數減一,該指針本身不會被銷毀。

  因為這里需要輸出引用計數,就沒有采用ARC,所以會有一個小問題,那就是當退出局部環境的時候,即使局部指針所指向的對象已被銷毀,局部指針變量的值仍然沒有改變,因此需要手動賦值為nil。如果采用ARC的話,會自動回收內存并將指針賦值為nil。

總結

  不管是直接通過指針賦值還是通過retain或者copy來保留對象,都會增加指向對象的指針數量,這些指針都指向同一塊內存地址,因為對象所分配的內存地址是不變的。

  指向對象的指針的多少跟引用計數沒有任何關系,但是通過retain、copy或release可以改變對象的引用計數。

  僅當引用計數為0時才會釋放對象占用的內存空間。  

  哎,真是“落花有意流水無情”啊,哪怕再多的指針“愛上對象”,人家這輩子卻也只認引用計數。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 阳西县| 体育| 绥化市| 延吉市| 安龙县| 房产| 彰化市| 长海县| 赤壁市| 天台县| 尖扎县| 奉新县| 晋州市| 衡阳市| 武冈市| 阆中市| 翼城县| 甘孜县| 河北区| 安新县| 凌海市| 浮山县| 察隅县| 资阳市| 独山县| 区。| 江安县| 滦南县| 宁阳县| 九龙城区| 海原县| 华安县| 马尔康县| 永定县| 阳曲县| 彰化县| 仙桃市| 永康市| 宝山区| 城市| 蒙城县|