Block的原理學(xué)習(xí)

Block的定義

  1. Blocks是C語言的擴(kuò)充功能跟继〉锞停可以用一句話來表示Blocks的擴(kuò)充功能:帶有自動(dòng)變量(局部變量)的匿名函數(shù)内狗。

  2. Block的語法聲明:返回值類型 (^變量名)(參數(shù)列表) = ^ 返回值類型 (參數(shù)列表) 表達(dá)式

    • 代碼表示:void (^block)(void) = ^void (void){};

    • 或者使用typedef來定義一個(gè)block:typedef void (^block)(void);

分類

在iOS中圈匆,主要有三種Block類型邻吭,NSGlobalBlock晤斩、NSStackBlock燎孟、NSMallocBlock。這三種block類型主要是以block所在的儲(chǔ)存空間作為區(qū)分依據(jù)尸昧。

  1. 全局Block:NSGlobalBlock

    • 位于內(nèi)存全局區(qū)揩页,.data區(qū)域

    • 在Block內(nèi)部,沒有訪問外部的變量

    • (__NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject)

  2. 棧Block:NSStackBlock

    • 位于內(nèi)存的棧存儲(chǔ)區(qū),

    • 在Block內(nèi)部爆侣,訪問外部變量萍程,但是只讀操作,不能進(jìn)行"寫"操作

    • (__NSStackBlock__ : __NSStackBlock : NSBlock : NSObject)

  3. 堆Block:NSMallocBlock

    • 位于內(nèi)存的堆存儲(chǔ)區(qū)

    • 堆Block是棧Block的復(fù)制兔仰,因此在Block內(nèi)部會(huì)訪問外部變量茫负,也是只讀操作,不能進(jìn)行"寫"操作

    • (__NSMallocBlock__ : __NSMallocBlock : NSBlock : NSObject)

  4. 三種類型存儲(chǔ)示意圖:

block類型.png
  1. Block的分類簡單用下圖表示


    Block分類.png

為了方便后續(xù)的分析和展示乎赴,需要把代碼編譯成.cpp文件忍法。編譯指令如下

  • 模擬器:xcrun -sdk iphonesimulator clang -rewrite-objc xxx文件名

  • 真機(jī): xcrun -sdk iphoneos clang -rewrite-objc xxx文件名

全局區(qū)Block(NSGlobalBlock)
  1. 沒有外部變量引用的block就是全局區(qū)Block,如下代碼:

      //全局block NSGlobalBlock
    -(void)globalBlock{
       void(^globalBlock)(void) = ^{
          NSLog(@">>>>>globalBlock<<<<<");
       };
       NSLog(@"當(dāng)前的block類型為>>>>>%@",globalBlock);
      globalBlock();
    }
    ------------------------------------------------------
    
    //打印結(jié)果為:
    2022-04-20 14:38:22.668397+0800 suanfaProject[5151:219121] 當(dāng)前的block類型為>>>>><__NSGlobalBlock__: 0x102b04cc0>
    2022-04-20 14:38:22.668573+0800 suanfaProject[5151:219121] >>>>>globalBlock<<<<<
    
  2. 編譯后的.cpp文件榕吼,很復(fù)雜饿序,直截取需要的內(nèi)容:

    struct __block_impl {
      //isa指針,指向具體的Block的類羹蚣,當(dāng)前是NSGlobalBlock類型的block原探,指向?yàn)開_NSGlobalBlock__
      void *isa;
      //表示一些block的附加信息,涉及到引用指數(shù)和銷毀的判斷顽素,會(huì)使用到該值
      int Flags;
      //保留變量
      int Reserved;
      // 函數(shù)指針咽弦,指向具體的 block 實(shí)現(xiàn)的函數(shù)調(diào)用地址
      //block將需要執(zhí)行的代碼創(chuàng)建一個(gè)函數(shù),impl內(nèi)部的FuncPtr指向這個(gè)函數(shù)的地址胁出,通過地址調(diào)用這個(gè)函數(shù)型型,就可以執(zhí)行block里面的代碼
      //函數(shù)名稱類似于 __類名__調(diào)用block的方法名_block_func_標(biāo)記數(shù) :__BlockClang__globalBlock_block_func_0
      void *FuncPtr;
    };
    
    // @implementation BlockClang
    ///這個(gè)結(jié)構(gòu)體就是block的實(shí)際內(nèi)容。
    struct __BlockClang__globalBlock_block_impl_0 {
      struct __block_impl impl;
      //block的描述信息全蝶,
      struct __BlockClang__globalBlock_block_desc_0* Desc;
      //結(jié)構(gòu)體的同名構(gòu)造函數(shù)
      //fp:是block是根據(jù)執(zhí)行的代碼而創(chuàng)建的函數(shù)的函數(shù)指針
      //desc:block的描述信息結(jié)構(gòu)體
      __BlockClang__globalBlock_block_impl_0(void *fp, struct __BlockClang__globalBlock_block_desc_0 *desc, int flags=0)   {
          //在編譯階段所有類型的block的isa都指向_NSConcreteStackBlock输莺,也就是說block類型是在運(yùn)行時(shí)確定的。
          impl.isa = &_NSConcreteStackBlock;
          impl.Flags = flags;
          impl.FuncPtr = fp;
          Desc = desc;
      }
    };
    
    //block將需要執(zhí)行的代碼創(chuàng)建一個(gè)函數(shù)
    static void __BlockClang__globalBlock_block_func_0(struct __BlockClang__globalBlock_block_impl_0 *__cself) {
        //里面是一句打印代碼
        NSLog((NSString*)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_8949bb_mi_0);
      }
    
    //block的描述信息結(jié)構(gòu)體裸诽,并給出一個(gè)默認(rèn)值
    static struct __BlockClang__globalBlock_block_desc_0 {
        size_t reserved;
        size_t Block_size;
     } __BlockClang__globalBlock_block_desc_0_DATA = { 0, sizeof(struct __BlockClang__globalBlock_block_impl_0)};
    
    //-(void)globalBlock的方法嫂用,編譯出來的文件
    static void _I_BlockClang_globalBlock(BlockClang * self, SEL _cmd) {
      //void(*globalBlock)(void) = &__BlockClang__globalBlock_block_impl_0(__BlockClang__globalBlock_block_func_0,   &__BlockClang__globalBlock_block_desc_0_DATA));
      void(*globalBlock)(void) = ((void (*)())&__BlockClang__globalBlock_block_impl_0((void       *)__BlockClang__globalBlock_block_func_0, &__BlockClang__globalBlock_block_desc_0_DATA));
    
      //打印函數(shù)
      NSLog((NSString *)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_8949bb_mi_1,globalBlock);
      //調(diào)用globalBlock
      //去掉類型強(qiáng)轉(zhuǎn)后的代碼:globalBlock->FuncPtr(globalBlock); //即調(diào)用globalBlock結(jié)構(gòu)體中FuncPtr參數(shù),傳值為block本身
      ((void (*)(__block_impl *))((__block_impl *)globalBlock)->FuncPtr)((__block_impl *)globalBlock);
    }
    // @end
    
  3. 總結(jié)以上述代碼

    • Block的輸出為__NSGlobalBlock__丈冬,這也就說明了嘱函,是一種全局Block

    • Block內(nèi)部沒有訪問外部的auto變量

棧Block(NSStackBlock)
  1. 非強(qiáng)引用Block或者非copy的Block,類型就是棧Block埂蕊。ARC下需要使用__weak修飾就是stackBlock往弓,代碼實(shí)例如下

     //棧block NSStackBlock
    -(void)stackBlock{
        int age = 10;
        void(^ stackBlock)(void) = ^{
            NSLog(@">>>>>stackBlock<<<<<, age : %d",age);
        };
        NSLog(@"當(dāng)前的block類型為>>>>>%@",stackBlock);
        stackBlock();
    }
    ------------------------------------------------------
    //打印結(jié)果為:
    2022-04-20 15:49:59.063036+0800 suanfaProject[5886:260026] 當(dāng)前的block類型為>>>>><__NSStackBlock__: 0x16b5c5938>
    2022-04-20 15:49:59.063267+0800 suanfaProject[5886:260026] >>>>>NSStackBlock<<<<<, age : 10
    
  2. 編譯后的.cpp文件,如下

     struct __block_impl {
        //isa指針蓄氧,指向具體的Block的類函似,當(dāng)前是NSGlobalBlock類型的block,指向?yàn)開_NSGlobalBlock__
        void *isa;
        //表示一些block的附加信息喉童,涉及到引用指數(shù)和銷毀的判斷撇寞,會(huì)使用到該值
        int Flags;
         //保留變量
        int Reserved;
        // 函數(shù)指針,指向具體的 block 實(shí)現(xiàn)的函數(shù)調(diào)用地址
        //block將需要執(zhí)行的代碼創(chuàng)建一個(gè)函數(shù),impl內(nèi)部的FuncPtr指向這個(gè)函數(shù)的地址蔑担,通過地址調(diào)用這個(gè)函數(shù)牌废,就可以執(zhí)行block里面的代碼
        //函數(shù)名稱類似于 __類名__調(diào)用block的方法名_block_func_標(biāo)記數(shù) :__BlockClang__globalBlock_block_func_0
        void *FuncPtr;
      };
    
      // @implementation BlockClang
      //block的結(jié)構(gòu)體
      struct __BlockClang__stackBlock_block_impl_0 {
        struct __block_impl impl;
        struct __BlockClang__stackBlock_block_desc_0* Desc;
    
        //這里多了一個(gè)age的參數(shù),是和全局Block的文件不同的地方
        int age;
        //結(jié)構(gòu)體的同名構(gòu)造函數(shù)
        //fp:是block是根據(jù)執(zhí)行的代碼而創(chuàng)建的函數(shù)的函數(shù)指針
        //desc:block的描述信息結(jié)構(gòu)體
        //多個(gè)一個(gè)age的賦值啤握,看的出來這里是一個(gè)值的復(fù)制鸟缕,并不是指針
        __BlockClang__stackBlock_block_impl_0(void *fp, struct __BlockClang__stackBlock_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
           impl.isa = &_NSConcreteStackBlock;
          impl.Flags = flags;
          impl.FuncPtr = fp;
          Desc = desc;
        }
      };
    
      //block將需要執(zhí)行的代碼創(chuàng)建一個(gè)函數(shù)
      static void __BlockClang__stackBlock_block_func_0(struct __BlockClang__stackBlock_block_impl_0 *__cself) {
          int age = __cself->age; // bound by copy
          //打印代碼
          NSLog((NSString *)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_7d0b37_mi_0,age);
      }
    
    
      //block的描述信息結(jié)構(gòu)體,并給出一個(gè)默認(rèn)值
      static struct __BlockClang__stackBlock_block_desc_0 {
          size_t reserved;
          size_t Block_size;
      } __BlockClang__stackBlock_block_desc_0_DATA = { 0, sizeof(struct __BlockClang__stackBlock_block_impl_0)};
    
      //-(void)stackBlock的方法排抬,編譯出來的文件
      static void _I_BlockClang_stackBlock(BlockClang * self, SEL _cmd) {
          //外部變量
          int age = 10;
          // void(* stackBlock)(void) = &__BlockClang__stackBlock_block_impl_0(__BlockClang__stackBlock_block_func_0, &__BlockClang__stackBlock_block_desc_0_DATA, age));
          void(* stackBlock)(void) = ((void (*)())&__BlockClang__stackBlock_block_impl_0((void *)__BlockClang__stackBlock_block_func_0, &__BlockClang__stackBlock_block_desc_0_DATA, age));
          //打印函數(shù)
          NSLog((NSString *)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_7d0b37_mi_1,stackBlock);
          //調(diào)用globalBlock
          //去掉類型強(qiáng)轉(zhuǎn)后的代碼:stackBlock->FuncPtr(stackBlock); //即調(diào)用globalBlock結(jié)構(gòu)體中FuncPtr參數(shù)懂从,傳值為block本身
          ((void (*)(__block_impl *))((__block_impl *)stackBlock)->FuncPtr)((__block_impl *)stackBlock);
      }
      // @end
    
  1. 給Block內(nèi)引用的外部變量age,進(jìn)行復(fù)制操作蹲蒲,觀察現(xiàn)象

    棧Block賦值操作.png

    由圖上可知番甩,在block內(nèi)部使用block外部定義的局部變量時(shí),在block內(nèi)部是只讀的悠鞍,不能對他進(jìn)行修改,如果想要修改模燥,變量前要有__block修飾咖祭,或者使用static修飾。

  2. 總結(jié)以上述代碼

    • Block的輸出為__NSStackBlock__蔫骂,這也就說明了么翰,是一種棧Block

    • 和全局Block不一樣的地方,在于Block結(jié)構(gòu)體中辽旋,有了需要引用的變量浩嫌,而且在Block結(jié)構(gòu)體初始化的時(shí)候,要進(jìn)行變量的賦值

    • 棧Block對于外部的變量补胚,是以值復(fù)制的方式進(jìn)行訪問的码耐。而且在編譯階段就已經(jīng)完成了賦值,如果外部變量更改值溶其,Block內(nèi)部的變量值是不變的

    • 在block內(nèi)進(jìn)行寫操作骚腥,編譯會(huì)報(bào)錯(cuò),提示需要使用__block進(jìn)行修飾瓶逃。__block的修飾原理束铭,在下面會(huì)有說明。

堆Block(NSMallocBlock)
  1. 強(qiáng)引用Block或者copy后的block厢绝,都是堆Block契沫,代碼如下
    //堆block NSMallocBlock
    -(void)mallocBlock1{
      int age = 10;
      //默認(rèn)是強(qiáng)引用
      void(^mallocBlock)(void) = ^{
          NSLog(@">>>>>NSMallocBlock<<<<<, age : %d",age);
      };
    
      NSLog(@"當(dāng)前的block類型為>>>>>%@",mallocBlock);
      mallocBlock();
    }
    ------------------------------------------------------
    //打印結(jié)果為:
    2022-04-21 14:39:23.197179+0800 suanfaProject[3997:179623] 當(dāng)前的block類型為>>>>><__NSMallocBlock__: 0x6000011b4120>
    2022-04-21 14:39:23.197343+0800 suanfaProject[3997:179623] >>>>>NSMallocBlock<<<<<, age : 10
    
  2. NSMallocBlock 類型的 block 通常不會(huì)在源碼中直接出現(xiàn)昔汉,因?yàn)槟J(rèn)它是當(dāng)一個(gè) block 被 copy 的時(shí)候懈万,才會(huì)將這個(gè) block 復(fù)制到堆中。以下是一個(gè) block 被 copy 時(shí)的示例代碼 (來自 這里),可以看到钞速,在圖中框起來的代碼贷掖,目標(biāo)的 block 類型被修改為_NSConcreteMallocBlock。
block的復(fù)制.png

Block的本質(zhì)

  1. 前面說過了Block的分類渴语,并且編譯出了各個(gè)分類的代碼苹威,在編譯出來的代碼中,最主要的代碼就是下面的這些:

    block結(jié)構(gòu)體.png

    在說明堆Block類型的時(shí)候驾凶,截圖的代碼中牙甫,是有一個(gè)void *_Block_copy(const void *arg)方法。三種類型的Block在使用的過程中调违,都會(huì)調(diào)用這個(gè)方法窟哺。其在方法的第一步就是struct Block_layout *aBlock;,獲取到對應(yīng)的Block技肩,而Block_layout結(jié)構(gòu)體就是Block的數(shù)據(jù)結(jié)構(gòu)且轨。Block_layout結(jié)構(gòu)體展示如下:

     #define BLOCK_DESCRIPTOR_1 1
    struct Block_descriptor_1 {
        uintptr_t reserved;
        uintptr_t size;
    };
    
    #define BLOCK_DESCRIPTOR_2 1
    struct Block_descriptor_2 {
        // requires BLOCK_HAS_COPY_DISPOSE
        BlockCopyFunction copy;
        BlockDisposeFunction dispose;
    };
    
    #define BLOCK_DESCRIPTOR_3 1
    struct Block_descriptor_3 {
        // requires BLOCK_HAS_SIGNATURE
        const char *signature;
        const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
    };
    
    struct Block_layout {
        void * __ptrauth_objc_isa_pointer isa;
        volatile int32_t flags; // contains ref count
        int32_t reserved;
        BlockInvokeFunction invoke;
        struct Block_descriptor_1 *descriptor;
        // imported variables
    };
    
  1. 在各個(gè)類型的Block打印中,可以看到各自對應(yīng)的結(jié)構(gòu)體類型

    • 全局Block -----> NSGlobalBlock

    • 棧Block. -----> NSStackBlock

    • 堆Block -----> NSMallocBlock

  2. 在Block開放的源碼中虚婿,data.m文件中可以發(fā)現(xiàn)旋奢,上述的3種Block都是繼承自NSObject

Block的類.png
  1. 綜上

    1. block底層就是一個(gè)Block_layout類型的結(jié)構(gòu)體然痊,這個(gè)結(jié)構(gòu)體中包含一個(gè)isa指針至朗,本質(zhì)上是一個(gè)OC對象

    2. block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境OC對象

Block變量截獲

  1. 為什么要進(jìn)行變量的捕獲?

    • 經(jīng)過前面Block類型的說明剧浸,了解到Block的三種類型锹引,分別存放在內(nèi)存全局區(qū)、內(nèi)存棧區(qū)唆香、內(nèi)存堆區(qū)嫌变,不同的類型,銷毀的時(shí)機(jī)不同躬它。

    • 在不同的Block內(nèi)部使用外部變量的時(shí)候初澎,需要考慮外部變量的生命周期,特別是銷毀的情況

    • 不能出現(xiàn)在Block內(nèi)部使用的使用虑凛,外部變量已經(jīng)銷毀碑宴。

    • 所以需要把外部變量捕獲到Block的內(nèi)部,這樣的話桑谍,使用的時(shí)候就不需要管外部的局部變量是不是還存在,只要使用內(nèi)部的捕獲變量就行

  2. C語言中變量分為三類

    • 全局變量: 作用域在全局延柠,哪個(gè)地方都能調(diào)用

    • 局部變量:作用域在大括號中,只能在大括號內(nèi)調(diào)用

      • 局部自動(dòng)變量 auto 關(guān)鍵字修飾

      • 局部靜態(tài)變量 static 關(guān)鍵字修飾

  3. 變量是對象的情況下锣披,也是一樣的贞间。對象類型的局部變量連所有權(quán)修飾符(__strong,__weak)一起捕獲

    • 棧Block:不管外部變量是強(qiáng)引用還是弱引用贿条,block都會(huì)弱引用訪問對象

    • 堆Block:

      • 如果外部強(qiáng)引用block內(nèi)部也是強(qiáng)引用

      • 如果外部弱引用增热,block內(nèi)部也是弱引用

  4. 下面展示不同變量在Block內(nèi)部的引用情況

    • auto變量在Block內(nèi)部的捕獲情況
    auto變量.png
    • 靜態(tài)變量在Block內(nèi)部的捕獲情況
    靜態(tài)變量.png
    • 全局變量在Block內(nèi)部的捕獲情況

      全局變量.png
  1. 由第四條得處一下結(jié)論

    變量捕獲.png

__block修飾符

  1. 前面在Block分類里說過整以,如果要在Block內(nèi)部修改引用的外部變量的值,直接修改是不允許的峻仇。編譯系統(tǒng)給的提示是需要使用__Block修飾符公黑。接下來就看一下__Block的邏輯。

    __block修飾符.png
  1. 在報(bào)錯(cuò)的代碼上進(jìn)行修改摄咆,外部變量增加__block修飾凡蚜,并且在Block內(nèi)部修改變量的值。

    //堆block NSMallocBlock
    -(void)mallocBlock1{
       //在Block需要引用的外部變量吭从,添加__Block修飾符
       __block int age = 10;
       //默認(rèn)是強(qiáng)引用
       void(^mallocBlock)(void) = ^{
         //在Block內(nèi)部修改變量的值
           age = 30;
           NSLog(@">>>>>NSMallocBlock<<<<<, age : %d",age);
       };
       NSLog(@"當(dāng)前的block類型為>>>>>%@",mallocBlock);
       mallocBlock();
    }
    ------------------------------------------------------
    打印結(jié)果為:
      2022-04-22 14:26:14.442743+0800 suanfaProject[2562:85326] 當(dāng)前的block類型為>>>>><__NSMallocBlock__: 0x60000092ebe0>
    2022-04-22 14:26:14.442810+0800 suanfaProject[2562:85326] >>>>>NSMallocBlock<<<<<, age : 30
    

    可以看出朝蜘,加了__block后,編譯不會(huì)報(bào)錯(cuò)涩金,并且谱醇,修改變量的值成功。

  2. 接下來就編譯出這段代碼的.cpp文件進(jìn)行查看

    // @implementation BlockClang
    //編譯器將__block修飾的外部變量包裝成一個(gè)結(jié)構(gòu)體(對象)步做,在結(jié)構(gòu)體中創(chuàng)建一個(gè)同名變量,
    struct __Block_byref_age_0 {
        void *__isa;
      //結(jié)構(gòu)體的指針指向
        __Block_byref_age_0 *__forwarding;
        int __flags;
        int __size;
      //同名變量
        int age;
    };
    //Block的結(jié)構(gòu)體副渴,這個(gè)結(jié)構(gòu)體就是block的實(shí)際內(nèi)容。
    struct __BlockClang__mallocBlock1_block_impl_0 {
        struct __block_impl impl;
        struct __BlockClang__mallocBlock1_block_desc_0* Desc;
    
        //block內(nèi)部捕獲根據(jù)外部變量創(chuàng)建的結(jié)構(gòu)體指針
        //將結(jié)構(gòu)體copy到堆上辆床,在block中使用自動(dòng)變量時(shí)佳晶,使用指針指向的結(jié)構(gòu)體中的自動(dòng)變量桅狠,于是就達(dá)到了修改外部變量的作用讼载。
        __Block_byref_age_0 *age; // by ref
    
        //block結(jié)構(gòu)體的同名構(gòu)造
        __BlockClang__mallocBlock1_block_impl_0(void *fp,struct __BlockClang__mallocBlock1_block_desc_0 *desc,__Block_byref_age_0 *_age,int flags=0) : age(_age->__forwarding) {
            impl.isa = &_NSConcreteStackBlock;
            impl.Flags = flags;
            impl.FuncPtr = fp;
            Desc = desc;
        }
    };
    //-(void)mallocBlock1的方法,編譯出來的文件
    static void _I_BlockClang_mallocBlock1(BlockClang * self, SEL _cmd) {
        //把__block修飾的變量包裝成__Block_byref_age_0結(jié)構(gòu)體中跌,并且玩層指針和變量的賦值
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
    
      //獲取實(shí)例化的block
        void(*mallocBlock)(void) = ((void (*)())&__BlockClang__mallocBlock1_block_impl_0((void *)__BlockClang__mallocBlock1_block_func_0, &__BlockClang__mallocBlock1_block_desc_0_DATA,(__Block_byref_age_0 *)&age,570425344));
    
        //打印語句
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_e5501c_mi_1,mallocBlock);
        //block的調(diào)用
        ((void (*)(__block_impl *))((__block_impl *)mallocBlock)->FuncPtr)((__block_impl *)mallocBlock);
    }
    
    //block內(nèi)部任務(wù)創(chuàng)建出來的方法
    static void __BlockClang__mallocBlock1_block_func_0(struct __BlockClang__mallocBlock1_block_impl_0 *__cself) {
        __Block_byref_age_0 *age = __cself->age; // bound by ref
    
        //這里是給__block修飾的外部變量的賦值操作咨堤,可以看出使用的是根據(jù)變量的指針找到內(nèi)存地址,然后進(jìn)行的賦值
        (age->__forwarding->age) = 30;
      //打印操作
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_e5501c_mi_0,(age->__forwarding->age));
    }
    
    /*
    //以下代碼暫不關(guān)注
    static struct __BlockClang__mallocBlock1_block_desc_0 {
        size_t reserved;
        size_t Block_size;
        void (*copy)(struct __BlockClang__mallocBlock1_block_impl_0*,
                     struct __BlockClang__mallocBlock1_block_impl_0*);
        void (*dispose)(struct __BlockClang__mallocBlock1_block_impl_0*);
    } __BlockClang__mallocBlock1_block_desc_0_DATA = { 0,
        sizeof(struct __BlockClang__mallocBlock1_block_impl_0),
        __BlockClang__mallocBlock1_block_copy_0,
        __BlockClang__mallocBlock1_block_dispose_0
    };
    
    
    static void __BlockClang__mallocBlock1_block_copy_0(struct __BlockClang__mallocBlock1_block_impl_0*dst, struct __BlockClang__mallocBlock1_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8);}
    
    static void __BlockClang__mallocBlock1_block_dispose_0(struct __BlockClang__mallocBlock1_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8);}
    */
    
    // @end
    
  3. 對比一下沒有使用__block的編譯文件

    使用__block的對比.png
    • 編譯器會(huì)將__block修飾的變量包裝成一個(gè)結(jié)構(gòu)體(對象)

    • 在結(jié)構(gòu)體中新建一個(gè)同名變量

    • block內(nèi)部捕獲該結(jié)構(gòu)體指針

    • 在block中使用外部變量時(shí)漩符,使用指針指向的結(jié)構(gòu)體中的外部變量

  4. 在對結(jié)構(gòu)體中的指針指向進(jìn)行說明

    • 如果Block是一個(gè)棧Block(NSStackBlock)一喘,結(jié)構(gòu)體中的__forwarding指針指向的就是自己所在區(qū)域的地址

    • 如果Block是一個(gè)堆Block(NSMallocBlock),由于堆Block是棧Block復(fù)制生成的嗜暴,即從棧區(qū)復(fù)制到了堆區(qū)凸克。則在堆區(qū)會(huì)有一塊屬于Block的空間,也就會(huì)有變量結(jié)構(gòu)體的空間闷沥。這個(gè)情況下萎战,__forwarding指針指向的在堆區(qū)的內(nèi)存地址。

      • a舆逃、當(dāng)block在棧時(shí)蚂维,__Block_byref_a_0結(jié)構(gòu)體內(nèi)的__forwarding指針指向結(jié)構(gòu)體自己
      • b戳粒、當(dāng)block被復(fù)制到堆中時(shí),棧中的__Block_byref_age_0結(jié)構(gòu)體也會(huì)被復(fù)制到堆中一份虫啥,而此時(shí)棧中的__Block_byref_a_0結(jié)構(gòu)體中的__forwarding指針指向的就是堆中的__Block_byref_a_0結(jié)構(gòu)體蔚约,堆中__Block_byref_a_0結(jié)構(gòu)體內(nèi)的__forwarding指針依然指向自己
      • c、通過__forwarding指針巧妙的將修改的變量賦值在堆中的__Block_byref_a_0中


        __block的 __forwarding指針.png

block循環(huán)引用以及解決方法

循環(huán)引用的原因

  1. 引用計(jì)數(shù)算法(Reference Counting)涂籽,對每個(gè)對象保存一個(gè)整型的引用計(jì)數(shù)器屬性苹祟。用于記錄對象被引用的情況。

    • 對于一個(gè)對象 A又活,只要有任何一個(gè)對象引用了 A苔咪,則 A 的引用計(jì)數(shù)器就加1;

    • 當(dāng)引用被銷毀時(shí)柳骄,引用放發(fā)通知給對象A鹦聪,引用計(jì)數(shù)器就減1汁掠。

    • 對象 A 的引用計(jì)數(shù)器一旦變?yōu)?,即表示對象 A 不可能再被使用,對象A被銷毀蔼夜。

  2. 正常釋放流程

    • 當(dāng)A持有B,當(dāng)A銷毀后钮热,會(huì)給B發(fā)送信號雌隅。B收到信號后,如果此時(shí)B的引用計(jì)數(shù)為0時(shí)臼婆,則B就會(huì)銷毀抒痒,此時(shí)A,B都能正常釋放颁褂。不會(huì)引起內(nèi)存泄漏
    正常引用.jpg
  1. 循環(huán)引用

    • 當(dāng)A持有B故响,B同時(shí)也持有A時(shí),此時(shí)A銷毀需要B先銷毀颁独,而B銷毀同樣需要A先銷毀彩届,就導(dǎo)致相互等待銷毀,此時(shí)A誓酒,B的引用計(jì)數(shù)都不為0樟蠕,所以A,B此時(shí)都無法釋放靠柑。 從而導(dǎo)致了內(nèi)存泄漏
    循環(huán)引用.jpg

解決方法

  1. 平常代碼里用的最多的應(yīng)該就是__weak修飾符寨辩。

    • 定義一個(gè)弱引用的weakSelf,即__weak typeof(self) weakSelf = self;歼冰,指向self靡狞。

    • 因?yàn)槭?code>__weak修飾,變量的引用計(jì)數(shù)不會(huì)增加

    • 使用__weak修飾的代碼展示

      - (void)weakSelf{
          self.name = @"jack";
          __weak typeof(self) weakSelf = self;
          void(^block)(void) = ^{
              NSLog(@"weakSelf.name>>>>>%@",weakSelf.name);
          };
          block();
      }
      ------------------------------------------------------
      打印結(jié)果為:
      2022-04-24 10:24:20.008072+0800 suanfaProject[1716:30969] weakSelf.name>>>>>jack
      
  2. weak-strong-dance (弱強(qiáng)共舞)

    • 如果只使用__weak修飾的變量停巷,其引用計(jì)數(shù)不會(huì)增加耍攘。這樣的話榕栏,會(huì)出現(xiàn)block內(nèi)部持有的對象被提前釋放的情況。比如在Block內(nèi)部執(zhí)行一個(gè)計(jì)時(shí)器任務(wù)蕾各,就會(huì)出現(xiàn)定時(shí)任務(wù)在執(zhí)行的時(shí)候扒磁,退出控制器,控制器就被銷毀式曲,其變量獲取不到

        - (void)weakSelfShortcoming{
          self.name = @"jack";
          __weak typeof(self) weakSelf = self;
          void(^block)(void) = ^{
              dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                  NSLog(@"weakSelf.name>>>>>%@",weakSelf.name);
              });
          };
          block();
      }
      ------------------------------------------------------
      2022-04-24 10:28:11.588892+0800 suanfaProject[1768:33723] ~~~ self dealloc ~~~
        //可以看出妨托,self已經(jīng)被銷毀,self的地址清空吝羞,也就獲取不到weakSelf.name
      2022-04-24 10:28:11.936690+0800 suanfaProject[1768:34131] weakSelf.name>>>>>(null)
      
    • Block內(nèi)部嵌套定時(shí)任務(wù)兰伤,則需要同時(shí)使用__weak__strong。如果只用weak修飾钧排,則可能出現(xiàn)block內(nèi)部持有的對象被提前釋放敦腔,為了防止block內(nèi)部變量被提前釋放,使用__strong對引用計(jì)數(shù)+1恨溜,防止提前釋放符衔。

        - (void)weak_strong_Self{
          self.name = @"jack";
          __weak typeof(self) weakSelf = self;
          void(^block)(void) = ^{
            //在Block內(nèi)部,使用__strong修飾
              __strong typeof(weakSelf)strongSelf = weakSelf;
              dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                  NSLog(@"weakSelf.name>>>>>%@",strongSelf.name);
              });
          };
          block();
      }
      ------------------------------------------------------
        //可以看出糟袁,self是在定時(shí)任務(wù)完成以后進(jìn)行銷毀的
      2022-04-24 10:35:18.498441+0800 suanfaProject[1869:38834] weakSelf.name>>>>>jack
      2022-04-24 10:35:18.499136+0800 suanfaProject[1869:38688] ~~~ self dealloc ~~
      
  3. __block修飾變量

    • 代碼如下
         - (void)blockSymbol{
          self.name = @"jack";
          __block BlocksVC *blockVC = self;
          void(^block)(void) = ^{
              NSLog(@"blockVC.name>>>>>%@",blockVC.name);
          };
          block();
      }
      ------------------------------------------------------
      2022-04-24 10:48:00.279485+0800 suanfaProject[2049:46359] blockVC.name>>>>>jack
      2022-04-24 10:48:00.625749+0800 suanfaProject[2049:46303] ~~~ self dealloc ~~~
      
  4. 把對象作為Block的參數(shù)判族,傳到Block內(nèi)部

    • 代碼如下

         self.name = @"jack";
         void(^block)(BlocksVC *blockVC) = ^(BlocksVC *blockVC){
             dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                 NSLog(@"blockVC.name>>>>>%@",blockVC.name);
             });
         };
         block(self);
      }
      ------------------------------------------------------
      2022-04-24 10:56:38.622366+0800 suanfaProject[2154:51224] blockVC.name>>>>>jack
      2022-04-24 10:56:38.623024+0800 suanfaProject[2154:51097] ~~~ self dealloc ~~~</pre>
      
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市项戴,隨后出現(xiàn)的幾起案子形帮,更是在濱河造成了極大的恐慌,老刑警劉巖周叮,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辩撑,死亡現(xiàn)場離奇詭異,居然都是意外死亡则吟,警方通過查閱死者的電腦和手機(jī)槐臀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門锄蹂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來氓仲,“玉大人,你說我怎么就攤上這事得糜【纯福” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵朝抖,是天一觀的道長啥箭。 經(jīng)常有香客問我,道長治宣,這世上最難降的妖魔是什么急侥? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任砌滞,我火速辦了婚禮,結(jié)果婚禮上坏怪,老公的妹妹穿的比我還像新娘贝润。我一直安慰自己,他們只是感情好铝宵,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布打掘。 她就那樣靜靜地躺著,像睡著了一般鹏秋。 火紅的嫁衣襯著肌膚如雪尊蚁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天侣夷,我揣著相機(jī)與錄音横朋,去河邊找鬼。 笑死百拓,一個(gè)胖子當(dāng)著我的面吹牛叶撒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播耐版,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼祠够,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了粪牲?” 一聲冷哼從身側(cè)響起古瓤,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎腺阳,沒想到半個(gè)月后落君,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亭引,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年绎速,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焙蚓。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纹冤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出购公,到底是詐尸還是另有隱情萌京,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布宏浩,位于F島的核電站知残,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏比庄。R本人自食惡果不足惜求妹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一乏盐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧制恍,春花似錦丑勤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至强挫,卻和暖如春岔霸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背俯渤。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工呆细, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人八匠。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓絮爷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親梨树。 傳聞我的和親對象是個(gè)殘疾皇子坑夯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

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