iOS的內存管理和引用計數規則、Block的用法以及三種形式(stack、malloc、global)

學習內容

  1. iOS的內存管理和引用計數規則objective-c

    • 內存管理的思考方式express

      • 本身生成的對象本身持有
      • 非本身生成的對象本身也能持有
      • 本身持有的對象不須要時釋放
      • 非本身持有的對象不能釋放
    • ARC有效時,id類型和對象類型必須加上全部權修飾符,一共有四種安全

      • __strong函數

        • id和對象類型若是不加全部權修飾符那麼默認爲__strong類型學習

        • id obj = [[NSObject alloc]init] 
          id __strong obj = [[NSObject alloc]init] 
          //以上兩種在ARC有效狀況下是相同的
        • //ARARC
          {
            id __strong obj = [[NSObject alloc]init]
          }
          //ARC無效時
          {
            id obj = [[NSObject alloc]init]
            [obj release]
          }
          //ARC無效時執行release操做
        • __strong修飾符表示對對象的強引用,持有強引用的變量在超出其做用域時被廢棄,它強引用的對象也會被釋放指針

        • 含有__strong修飾的變量,不單單在做用域上,在賦值上也能正確管理對象的生命週期code

      • __weak對象

        • __strong全部權修飾符大部分狀況下能夠完美的進行內潤管理,可是引用計數式內存管理方法必然會遇到循環引用,這時候就須要使用__weak來解決
        • 循環引用會形成內存泄漏,所謂內存泄漏就是應當被廢棄的對象在超出其生存週期(做用域)後繼續存在
        • __weak修飾的變量不持有對象,原對象在超出其做用域時會被當即釋放
        • 經過檢查__weak修飾符的變量是否爲nil,能夠判斷被賦值的對象是否已經被釋放
      • __unsafe_unretained生命週期

        • 這是不安全的全部權修飾符
        • 附有__unsafe_unretained的變量不屬於編譯器的內存管理對象
        • 若是對象已經被釋放,可是__unsafe_unretained修飾的變量仍然訪問了原對象的內存,那麼這個變量就是個懸垂指針,錯誤訪問,大機率會引發程序的崩潰
      • __autoreleasing內存

        • 在ARC有效時,用@autoreleasepool塊代替NSAutoreleasePool類,用附有__autoreleasing修飾符的變量來代替autorelease方法
        • 編譯器會自動檢查方法名是否爲alloc/new/copy/mutablecopy開頭,若是不是的話則自動將返回值的對象註冊到autoreleasepool中(init方法返回值的對象也不註冊到autoreleasepool中)
    • 屬性聲明的屬性修飾符與全部權修飾符之間的關係

      • 屬性修飾符 _unsafe_unretained
        assign _unsafe_unretained
        copy _strong(指針變量指向的是新的被複制的對象)
        retain _strong
        strong _strong
        unsafe_unretained _unsafe_unretained
        weak _weak
      • 爲屬性添加各類修飾符就至關於給變量添加各類對應的全部權修飾符

      • @property (strong) id obj;
        --------------------------
        id __strong obj = [[NSObject alloc]init]
    • ARC的規則

      • 不能使用retain/retainCount/release/autoRelease
      • 不能使用NSAllocateObject和NSDeallocateObject
      • 遵照內存管理的方法命名規則
      • 不要顯式的調用dealloc
      • 使用@autoreleasepool代替NSAutoreleasePool
      • 不能使用NSZone
      • 對象型變量不能做爲c語言結構體的成員
      • 顯式轉換"id"和"void *"
  2. Block

    • block是什麼

      • //一句話歸納,block是帶有自動變量的匿名函數
        ^(int param){
          	NSLog(@"%d",param);
        }
        -------------------------------------
        //上面的爲簡寫的,完整的block形式爲
        //^返回值類型 (參數列表){表達式}
        ^void (int param){
          	NSLog(@"%d",param);
        }
        //完整形式的block語法與C語言函數定義相比,僅有兩點不一樣
        //1.沒有函數名---沒有函數名由於它是匿名函數
        //2.帶有"^"符號---^是插入記號,方便查找
      • block的返回值類型能夠省略,省略返回值類型時

        • 若是表達式中有return語句,那麼返回值類型就和return的相同
        • 沒有return,返回值類型就是void
        • 有多個return語句時,全部return的類型必須相同
      • block的參數類型也能夠省略

        • ^(void)(void){expression}
          -------------------------
          ^{expression}
      • //C語言的函數指針的寫法
        int func(int a){
          printf("%d",a);
        }
        int (*funcptr)(int) = &func	//將func函數的地址賦值給函數指針funcptr
          
        //block的寫法
        int (^blk)(int a);	//僅僅是將c語言函數指針的"*"更換成了"^"
      • block類型變量與c語言變量的用法徹底相同,能夠做爲如下用途使用

        • 自動變量(局部變量)
        • 函數參數
        • 靜態變量
        • 靜態全局變量
        • 全局變量
      • block變量的使用

        • //將block賦值給block變量
          int (^blk)(int) = ^(int){};
          //將block變量賦值給block變量
          int (^blk1)(int) = blk;
          //兩個block變量互相賦值
          int (^blk2)(int);
          blk2 = blk1;
        • //在函數參數中使用block類型變量能夠向函數傳遞block
          void func((int)(^blk)(int));
          //在函數返回值中使用block能夠將返回值指定爲block
          void func(){
            return ^{return;};
          }
        • //使用typedef定義block
          typedef (int)(^blk)(int);	//定義一個block,後面該block的類型就爲blk
          //經過block類型變量調用block與在c語言中一般的函數執行沒什麼區別
          blk func(blk block,int rate){
          	return blk(rate);
          }
        • //這裏blk截獲的是Array對象的實例指針,經過這個實例指針調用該對象的方法是徹底沒問題的,可是若是向Array指針賦值的話就會編譯錯誤(能夠用__block解決)
          id Array = [NSMutableArray new];
          void (^blk)(void) = ^{
            id obj = [NSObject new];
            [Array addObject:obj];
          };
        • 在block中若是須要改變被截獲的外部變量的值,可使用__block說明符(__block存儲域類說明符)來解決

        • block代碼轉換爲cpp代碼分析(待完善)

          //block對外部變量捕獲的原理,使用cpp代碼查看
          //這裏寫一個block捕獲外部變量val的值
          int val = 10;
          void (^blk)(void) = ^{
            printf("%d",val);
          };
          //block本質也是一個OC的對象,oc對象都是結構體,其中含有指向父類的isa指針
          struct __main_block_impl_0 {
            struct __block_impl impl;
            struct __main_block_desc_0* Desc;
            int val;
            __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
              impl.isa = &_NSConcreteStackBlock;
              impl.Flags = flags;
              impl.FuncPtr = fp;
              Desc = desc;
            }
          };
          //這裏使用clang -rewrite-objc將.m代碼轉換成.cpp代碼,這裏的_cself是block的自身的結構體指針,能夠看到在block的函數中是將val從新建立了一個變量進行輸出
          static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            int val = __cself->val; // bound by copy
            printf("%d",val);
          }
        • 三種block的形式

          1. NSConcreteGlobalBlock(全局Block,存放在全局區[數據區])

            • 記錄全局變量的地方有Block語法時
            • Block語法的表達式中不使用應截獲的自動變量時
          2. NSConcreteStackBlock(棧Block)

            • 除了1.中的兩種狀況生成的Block,其餘使用Block語法產生的Block都是棧Block
          3. NSConcreteMallocBlock(堆Block)

            • 配置在全局區的block在變量做用域外也能夠經過指針安全的訪問,可是配置在棧上的block一旦其做用域結束就會被系統回收

            • Block提供了將Block和__Block變量複製到堆上的方法來解決這個問題

            • 複製到堆上的block將類對象NSMallocBlock賦值給isa指針

              struct __main_block_impl_0 {
                struct __block_impl impl;
                struct __main_block_desc_0* Desc;
                __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
                  //注意這裏就是講isa指針指向堆Block
                  impl.isa = &_NSConcreteStackBlock;
                }
              };
相關文章
相關標籤/搜索