[內(nèi)存管理進階系列一] - 內(nèi)存管理命名規(guī)則與修飾符

細節(jié)決定成敗,iOS移動開發(fā)發(fā)展已經(jīng)多年,相信內(nèi)存管理這個概念已經(jīng)被說爛了,但是相信對于很多小伙伴來說,只是了解了內(nèi)存管理的大概知識體系,對于一些邊緣知識點掌握的并不是很好,邊緣不代表不重要.
互聯(lián)網(wǎng)環(huán)境寒冬將至,為了應(yīng)對目前市場越來越高的需求,iOS開發(fā)不得不去提升自己的能力去匹配更高的需求,當每個人都學會了的時候,你比別人更好的體現(xiàn)就是你會的內(nèi)容更深入,進入正題,下面是總結(jié)的一些內(nèi)存管理方面的使用注意事項.

  1. 內(nèi)存管理方法名命名規(guī)則
    • alloc/new/copy/mutable
      以上名稱開頭的方法名,返回自己生成并持有的對象,如果沒有引用的變量則會釋放并廢棄.

          // 示例
          // main.m實現(xiàn)如下
          #import <Foundation/Foundation.h>
          #import "CFObject.h"    
      
          int main(int argc, const char * argv[]) {
              @autoreleasepool {
                  
                  id __weak obj;
                  @autoreleasepool {
                      obj = [CFObject newObject];
                      
                      NSLog(@"%@", obj);
                  }
                  
                  NSLog(@"%@", obj);
              }
              return 0;
          }   
      
          //CFObject.h中做函數(shù)聲明 
          // CFObject.m實現(xiàn)如下
          + (id)allocObject {
              return [[CFObject alloc] init];
          }   
      
          + (id)newObject {
              return [[CFObject alloc] init];
          }   
      
          // 打印結(jié)果為null null
          /**
              結(jié)果分析:
              由于obj指針為__weak弱指針,所以并不持有生成的對象,編譯器判斷沒有持有當前對象的變量,所以在newObject方法執(zhí)行結(jié)束后將對象釋放并廢棄,obj此時指向nil,第一個log打印null      
              第二個log打印時obj也不再指向?qū)ο?所以obj指針指向nil,打印結(jié)果為null
          */ 
      

      你可能會說,這不就是內(nèi)存管理最基本的原則嗎,不用了就釋放.這進階什么了,剛學的時候就已經(jīng)理解了.別急,繼續(xù)看下面的例子

          // 在CFObject.m中增加一個createObject方法,返回實例對象
          + (id)createObject {
              return [[CFObject alloc] init];
          }   
      
          // main.m中和上一個示例一樣調(diào)用并打印
          int main(int argc, const char * argv[]) {
              @autoreleasepool {
                  
                  id __weak obj;
                  
                  @autoreleasepool {
                      obj = [CFObject createObject];
                      
                      NSLog(@"%@", obj);
                  }
                  
                  NSLog(@"%@", obj);
              }
              return 0;
          }   
      
          /**
              猜一下打印結(jié)果是什么,還是null, null 嗎? 
              結(jié)果是 : <CFObject: 0x102805ef0> 和 null 
              為什么返回實例的實現(xiàn)都一樣,更換了方法名就打印不一樣了呢,這就引出了第二條命名規(guī)則
          */
      
    • 非第一條提到的名稱命名的方法名
      除了第一條以外命名的方法名,例如createObject NSMutablArray的 array方法 [NSMutablArray array],這一類的方法返回實例時并不持有對象,但是為了能達到使用對象的目的,使用了autorelease自動釋放池管理對象內(nèi)存.
      ```
      // 所以上面示例中的createObject方法實現(xiàn)可以轉(zhuǎn)換為返回autorelease 對象.類似以下實現(xiàn)(僅邏輯類比,并非真正實現(xiàn))
      + (id)createObject {
      return [[[CFObject alloc] init] autorelease];
      }

            /**
            結(jié)果分析 : 由于createObject返回實例對象被自動釋放池管理,所以不會立馬釋放對象,當一個log打印時,當前對象存在,可以有打印結(jié)果,
            當autoreleasepool{}結(jié)束作用域時,對象被釋放,所以第二個打印為null.
            */
        ```
      
    • ARC下第三條規(guī)則(引用自O(shè)bjective-C高級編程一書中Page 52 內(nèi)容)
      init:init方法規(guī)則比alloc/new/copy/mutableCopy 還要嚴格,該方法:
      1. 必須返回的是實例方法
      2. 返回對象類型為id或者聲明類的對象類型,或者是該類的父類或子類
      3. 返回對象不注冊到autoreleaspool上,只是對alloc返回的對象初始化并返回該對象,init方法源碼中調(diào)用runtime的_objc_rootInit直接將返回的對象返回,未作處理.(參考runtime源碼NSObject.mm的init方法實現(xiàn))

  2. 內(nèi)存管理修飾符詳解
    • 修飾符
      • __strong : 強引用指針,用于ARC下對變量修飾并產(chǎn)生強引用持有賦值對象,默認環(huán)境下的修飾符,一般 id object 書寫格式默認就是id __strong object.使用非常常見.
      • __weak:弱引用指針,用于ARC下對變量修飾產(chǎn)生弱引用,不持有對象,常用于解決循環(huán)引用問題.當指向?qū)ο蟛淮嬖跁r被置為nil.
        • 不支持__weak的情景:
          • 大多數(shù)重寫了retain/release等內(nèi)存管理方法的類,由于其自己實現(xiàn)了引用機制,而__weak需要用到一些runtime運行時庫的函數(shù),所以此種情況的類不支持__weak修飾,一般聲明中會有提示,并且編譯器編譯時報錯
          • 實現(xiàn)了allowsWeakReference或者retainWeakReference方法并且返回NO的情況
            • 當實現(xiàn)allowsWeakReference方法返回No時表示不支持弱引用,在賦值給__weak修飾的變量時,程序會異常終止
            • 當實現(xiàn)retainWeakReference方法返回NO時,__weak修飾的指針變量會被置為nil.
                // 還是用以上示例中的CFObject類實現(xiàn)方法如下
                - (BOOL)retainWeakReference  {
                    return YES;
                }
            
                // main.m做以下測試
                int main(int argc, const char * argv[]) {
                    @autoreleasepool {
                        id object = [[CFObject alloc] init];
                        
                        id __weak obj = object;
                    
                        
                        NSLog(@"%@", obj);
                    }
                    return 0;
                }
            
                /**
                    當return NO時,打印null;
                    當return YES時,打印對象
                */
            
        • __weak修飾的變量使用時會調(diào)用objc_loadWeakRetained()/objc_release()方法對做retain/release操作.大量使用__weak會產(chǎn)生性能損耗(大量做retain/release操作),
      • __autorelease
        • C中動態(tài)數(shù)組不支持autorelease(參考OC高編 P65)
        • autorelease修飾auto自動變量限制(僅限于局部變量,函數(shù)以及方法參數(shù))
        • 非顯示使用__autorelease修飾也生效的兩種情況:
          • 第一種就是命名規(guī)則提到的第二條,雖然沒顯示的__autorelease修飾,但是編譯器會做添加邏輯處理
          • 類似id obj 是默認 __strong修飾一樣,如果是對象的指針id *obj 或者NSObject ** object 這樣,默認是__autorelease.示例就是當傳入?yún)?shù)為對象指針類型,例如NSError的使用情景下,(該情況本人未通過代碼驗證,參考文章是OC 高級編程一書中的介紹)
              NSError *error;
          
              // 聲明
              method:(NSError **)error;
          
              // 調(diào)用
              method:&error
          
              // 此時method:(NSError **)error;聲明時,可以看做是method:(NSError * __autorelease *)error;
          
        • autorelease 和__strong 共同使用時優(yōu)化方案;
          當遇到命名規(guī)則中的第二條情況時,方法返回的對象需要做autorelease處理,如果對象賦值給__strong修飾的變量則又會做一個retain處理,示例如下:
              + (id)createObject {
                  return [[CFObject alloc] init];
              }  
          
              // main.m
              id obj = [CFObject createObject];
          
              clang編譯成c++代碼之后,
          
              // CFObject.m
              + (id)createObject {
                  id tem1 = ((id(*)(id, SEL))objc_msgSend)([NSObject class], @selector(alloc));
                  id tem2 = ((id(*)(id, SEL))objc_msgSend)(tem1, @selector(init));
          
                  return objc_autoreleaseReturnValue(id2);
              }
          
              // main.m
              id obj_tmp = ((id(*)(id, SEL))objc_msgSend)(self, @selector(obj));
              id obj     = objc_retainAutoreleasedReturnValue(obj_tmp);
              /**
                  main.m中,編譯器會在返回為autorelease對象賦值后自動插入objc_retainAutoreleasedReturnValue()方法, 此時先autorelease,然后再retain放到自動釋放池對象的操作會顯得很沒效率.
                  apple其實是對這個做了優(yōu)化處理的,處理邏輯如下(參考OC 高級編程 P67):
                      1. 在類似命名規(guī)則第二條情況時,返回autorelease對象是通過函數(shù)objc_autoreleaseReturnValue()函數(shù)實現(xiàn)的.
                      使用到objc_autoreleaseReturnValue()函數(shù)時,編譯器并不會決定立馬將對象放入自動釋放池,而是去查看調(diào)用方的執(zhí)行命令列表,是否會有objc_retainAutoreleasedReturnValue()操作,如果沒有,則按照正常的流程將對象放入自動釋放池,如果有調(diào)用,則不再方式釋放池,而是通過修改某個全局數(shù)據(jù)結(jié)構(gòu)標志位的方式,當調(diào)動到objc_retainAutoreleasedReturnValue()方法時,會去檢測該標志位,如果已經(jīng)設(shè)置修改過,則不會再做retain操作,
                      通過修改標志位的方式效率比retain/release 會更高
              */
          
      • unsafe_unretain : 同__weak一樣,都是弱引用,差異區(qū)別會放在下一知識點說明.
    • __weak 和unsafe_unretain 修飾符的差異
      • 空指針骇塘、野指針占贫、懸垂指針的差異
        空指針很好理解,就是指向NUll的指針,但是針對野指針(Wild pointer)和懸垂指針(Dangling pointer)則有不同的理解.
        在某些語言中,是不區(qū)分野指針和懸垂指針的,因為會Wild pointer 會被導向至 Dangling pointer ;對懸垂指針的理解是指向了已經(jīng)被釋放的內(nèi)存地址,但是指針未被置空.對野指針的理解是未初始化的指針或者是前面懸垂指針的情況,所以一般OC里也會說指向被釋放內(nèi)存地址的指針未野指針.對二者沒有明顯的區(qū)分.
      • 二者差異以及使用場景
        __weak 和unsafe_unretained都是弱引用指針,區(qū)別在于__weak是ARC下的弱引用,對不存在ARC機制以前使用unsafe_unretained修飾,從名字也可以看出后者是不安全的,即當對象被釋放廢棄后,指針不會被置為nil,還指向原來的內(nèi)存空間,此時使用有可能會導致程序異常.
  3. 如何獲取引用計數(shù)值
    • 因為在ARC環(huán)境下不允許使用ratainCount(MRC支持),所以如何獲取引用計數(shù)來調(diào)試內(nèi)存管理呢,
    • OC對象獲取方式:_objc_rootRetainCount()
    • C對象獲取方式:CFGetRetainCout()
      獲取引用計數(shù)是蘋果思想里面不推薦的,因為引用計數(shù)并不是非常明顯的代表著內(nèi)存管理的結(jié)果.例如autorelease下的引用計數(shù),會默認在autoreleasepool pop時 做release操作,但是當你打印時還未執(zhí)行pop操作就會導致和你認為的數(shù)值不符,我們應(yīng)該換一種角度看待引用計數(shù)的思想,他并不是真的想讓開發(fā)者根據(jù)數(shù)值去控制內(nèi)存管理,真正的思想是根據(jù)是否存在強引用的思想去理解內(nèi)存管理.通過成對的retain/release去控制引用.所以此處的引用計數(shù)只是作為debug的一個參考,不可太過依賴.

另外知其然,知其所以然.對于內(nèi)存管理相關(guān)的源碼分析會放在以后幾章節(jié)詳細說明.
還在學習的路上奔波,所以有的知識理解還存在很大誤區(qū),非常歡迎指出共同進步...

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市跛十,隨后出現(xiàn)的幾起案子唐含,更是在濱河造成了極大的恐慌浅浮,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捷枯,死亡現(xiàn)場離奇詭異滚秩,居然都是意外死亡,警方通過查閱死者的電腦和手機淮捆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門郁油,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人攀痊,你說我怎么就攤上這事桐腌。” “怎么了苟径?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵案站,是天一觀的道長。 經(jīng)常有香客問我棘街,道長蟆盐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任遭殉,我火速辦了婚禮石挂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘险污。我一直安慰自己痹愚,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拯腮,像睡著了一般窖式。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上疾瓮,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天脖镀,我揣著相機與錄音,去河邊找鬼狼电。 笑死蜒灰,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的肩碟。 我是一名探鬼主播强窖,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼削祈!你這毒婦竟也來了翅溺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤髓抑,失蹤者是張志新(化名)和其女友劉穎咙崎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吨拍,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡褪猛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了羹饰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伊滋。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖队秩,靈堂內(nèi)的尸體忽然破棺而出笑旺,到底是詐尸還是另有隱情,我是刑警寧澤馍资,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布筒主,位于F島的核電站,受9級特大地震影響鸟蟹,放射性物質(zhì)發(fā)生泄漏物舒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一戏锹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧火诸,春花似錦锦针、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悉盆。三九已至,卻和暖如春馋吗,著一層夾襖步出監(jiān)牢的瞬間焕盟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工宏粤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脚翘,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓绍哎,卻偏偏與公主長得像来农,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子崇堰,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容