block詳細(xì)了解及底層探索

block 三種類(lèi)型

全局block NSGlobalBlock
   void (^block)(void) = ^{
        NSLog(@"hahah");
    };
    
    NSLog(@"%@", block);
  • 沒(méi)有對(duì)外界變量進(jìn)行捕獲的時(shí)候澎迎,它是個(gè)函數(shù)的區(qū)域硕舆,直接放在全局區(qū)寄悯,方便執(zhí)行調(diào)用。
堆區(qū)block NSMallocBlock
   int a = 10;
    void (^block)(void) = ^{
        NSLog(@"Cooci - %d",a);
    };

    NSLog(@"%@",block);
  • 訪問(wèn)外界變量的時(shí)候艰争,它將進(jìn)行一些處理坏瞄,因?yàn)樵L問(wèn)的變量可能在棧區(qū),堆區(qū)甩卓,如果在全局區(qū)鸠匀,去訪問(wèn)會(huì)比較麻煩,所以block進(jìn)行了相應(yīng)的copy。copy到了相應(yīng) 的一些區(qū)域逾柿。所以向上面進(jìn)行了一次強(qiáng)引用的時(shí)候缀棍。此時(shí)是堆block.
棧區(qū)block NSStackBlock

這里有個(gè)坑點(diǎn),在iOS14之前 在block沒(méi)有進(jìn)行copy處理的時(shí)候它是一個(gè)棧區(qū)block机错,而之后卻放在了堆里爬范。

  NSLog(@"%@",^{
        NSLog(@"Cooci - %d",a);
    });
 
  • iOS14之前 為棧區(qū),也就是ARC下沒(méi)有被持有的話弱匪,向上面寫(xiě)法為棧區(qū)青瀑。
  • 而現(xiàn)在在堆區(qū)。

棧區(qū)的block寫(xiě)法

   int a = 10;
    void (^__weak block)(void) = ^{
        NSLog(@"Cooci - %d",a);
    };

    NSLog(@"%@",block);
  • 引用了外部變量痢法,當(dāng)此時(shí)對(duì)block進(jìn)行了了一次弱引用它就在棧區(qū)狱窘。

block 循環(huán)引用

正常釋放

當(dāng) A 對(duì)象 持有 B對(duì)象 的時(shí)候,B對(duì)象 的引用計(jì)數(shù) 會(huì)+1


截屏2021-07-06 下午3.35.11.png

當(dāng)A釋放的時(shí)候會(huì)給 B 信號(hào)财搁,B接收到 release信號(hào)蘸炸,引用計(jì)數(shù) -1 等于0的時(shí)候 b的dealloc就會(huì)被調(diào)用


截屏2021-07-06 下午3.52.21.png
循環(huán)引用

當(dāng) A 持有 B ,B也持有 A 尖奔,你中有我 我中有你的情況搭儒。就會(huì)造成循環(huán)引用。


截屏2021-07-06 下午3.59.49.png
循環(huán)引用代碼示意
   ///會(huì)發(fā)生循環(huán)引用
   self.block = ^(void){
         NSLog(@"%@",self.name);
    };
 ///不會(huì)發(fā)生循環(huán)引用
 [UIView animateWithDuration:0.2 animations:^{
        NSLog(@"%@",self.name);
    }];
  • 上面的情況我們都知道提茁,對(duì)于發(fā)生循環(huán)引用 我們?cè)撛趺唇鉀Q呢淹禾?
解決打破循環(huán)引用。

1茴扁、__weak typeof(self)weakSelf = self

__weak  typeof(self)weakSelf = self
   self.block = ^(void){
        NSLog(@"%@", weakSelf.name);
   };
  • 這種情況我們都知道用這個(gè)方法來(lái)打破那為什么铃岔?
  • 沒(méi)有打破之前 的樣子是這樣的 self ->block -> self(self持有block,block持有self) ,
    而打破之后 就是這樣 self -> block ->weakSelf ->self (self持有block,block持有 weakSelf,weakSelf持有 self.)那這樣就不會(huì)導(dǎo)致循環(huán)引用了么?weakSelf也持有者 self呢呀
  • 因?yàn)? weakSelf 是弱引用表中的峭火,和當(dāng)前的self是同一個(gè)指針地址毁习。__weak并不會(huì)導(dǎo)致self的引用計(jì)數(shù)發(fā)生變化。

那這樣就沒(méi)問(wèn)題了嗎看下面

   __weak typeof(self) weakSelf = self;
      self.block = ^(void){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakSelf.name);
        });
    };
    self.block();

此時(shí)我們發(fā)現(xiàn) 當(dāng)前頁(yè)面確實(shí)走了 dealloc卖丸。但是 當(dāng) 延時(shí)任務(wù)回來(lái)的時(shí)候 纺且,卻發(fā)現(xiàn) 打印的為nil. 雖說(shuō)一個(gè)打印任務(wù)并無(wú)商大雅。但是當(dāng)里面執(zhí)行的任務(wù)為很重要的時(shí)候稍浆。我還沒(méi)走完你就 dealloc载碌,顯然不符合我的要求猜嘱。所以我們正確的用法為 weak - strong -Dance 強(qiáng)弱共舞,保證self的聲明周期。

__weak typeof(self) weakSelf = self;
   self.block = ^(void){
        // 時(shí)間 - 精力
        // self 的生命周期
        __strong __typeof(weakSelf)strongSelf = weakSelf; // 可以釋放 when
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelf.name);
        });
    };
    self.block();
  • 在這里我們可以看到 __weak 打破了循環(huán)引用嫁艇。
  • __strong 延長(zhǎng)了 self的生命周期朗伶。

2丹允、通過(guò)傳參的形式將self 傳進(jìn)block任務(wù)中雌桑。

    self.block = ^(ViewController *vc){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
        });
    };
    self.block(self);
  • 這樣的話就打破了block對(duì)vc的持有,此時(shí)vc是已傳參的形式悦昵,它在block里就相當(dāng)于一個(gè)臨時(shí)變量被壓棧進(jìn)來(lái)歧斟。

3、主動(dòng)打破循環(huán)

   __block ViewController *vc = self;
    self.block = ^(void){
         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
           NSLog(@"%@",vc.name);
          vc = nil;
         });
   };
     self.block();
  • __block 為了可以在block里可以進(jìn)行修改偏形。
  • vc = nil 是為了打破循環(huán)引用静袖。
  • 注意:此時(shí)不調(diào)用循環(huán)引用依舊會(huì)存在。

4俊扭、NSProxy 也可以,這里就不講了队橙,自行搜索。

底層探究

定義一個(gè)簡(jiǎn)單的.c文件 如下 萨惑;

int main(){
      void(^block)(void) = ^{
    
        printf("LG_Cooci");
    };
     //block();
    return 0;
}
  • clang 查看 底層被編譯成了什么樣 xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c
int main(){
     ///簡(jiǎn)化去掉返回值類(lèi)型
    void(*block)(void) =  
&__main_block_impl_0  (  __main_block_func_0  ,  &__main_block_desc_0_DATA ) ;


    return 0;
}
  • 清晰的看到一個(gè)函數(shù)__main_block_impl_0 和兩個(gè)參數(shù) 參數(shù)1: __main_block_func_0參數(shù)2:__main_block_desc_0_DATA

查看 __main_block_impl_0這個(gè)函數(shù)

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 flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
    
};
  • 可以看到 __main_block_impl_0這個(gè)函數(shù)它是一個(gè)結(jié)構(gòu)體捐康,也就是說(shuō)block是一個(gè)__main_block_impl_0類(lèi)型的對(duì)象。

  • 里面有兩個(gè) 結(jié)構(gòu)體成員 一個(gè)為__block_impl 一個(gè)為 __main_block_desc_0類(lèi)型
    __block_impl 結(jié)構(gòu)類(lèi)型

     struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    

    __main_block_desc_0結(jié)構(gòu)類(lèi)型

     static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
          printf("LG_Cooci");
    }
    
    static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    
  • 在看一下它(__main_block_impl_0)的構(gòu)造函數(shù)庸蔼。

    1. 給第一個(gè)結(jié)構(gòu)體__block_impl類(lèi)型的 成員 impl賦值解总。
      impl 的 isa成員賦值 為 棧block 類(lèi)型;
      implFlages設(shè)置了個(gè)標(biāo)記姐仅;
      implFuncPtr成員 賦值為外界 傳進(jìn)來(lái)的 __main_block_func_0 函數(shù)花枫。
    2. 給第二個(gè)成員Desc 賦值外界傳進(jìn)來(lái)的__main_block_desc_0_DATA的地址。
  • 畫(huà)圖表示一下這個(gè)結(jié)構(gòu)


    截屏2020-11-28 上午11.16.49.png
  • 總結(jié): block的本質(zhì) 是一個(gè)結(jié)構(gòu)體 也可以說(shuō)是一個(gè)對(duì)象掏膏,它內(nèi)部有兩個(gè)屬性劳翰,一個(gè)來(lái)存放 塊任務(wù)的,方法 及設(shè)置當(dāng)前塊任務(wù)類(lèi)型的isa馒疹。另一個(gè)屬性來(lái)計(jì)算當(dāng)前自己結(jié)構(gòu)體所占空間大小佳簸。

下面 我們看一下block是如何發(fā)起調(diào)用的。

依舊是這段代碼颖变,打開(kāi) 下方的 block()調(diào)用生均。

int main(){
      void(^block)(void) = ^{
    
        printf("LG_Cooci");
    };
    
    block();
    return 0;
}

clang 編譯期源碼

    void(*block)(void) =  
&__main_block_impl_0  (  __main_block_func_0  ,  &__main_block_desc_0_DATA ) ;
  
 ((__block_impl *)block)->FuncPtr)((__block_impl *)block);

看到這里 我們就明白了,此時(shí)發(fā)起調(diào)用悼做,它是 將 block指針強(qiáng)轉(zhuǎn)為__block_impl類(lèi)型疯特。并獲取之前存入的 FuncPtr 發(fā)起函數(shù)調(diào)用,并將 block指針作為參數(shù)傳入肛走。

block如何捕獲外界變量的

int main(){
      
    int a =10;
    void(^block)(void) = ^{
    
        printf("LG_Cooci%d",a);
    };
    
    block();
    return 0;
}

clang

int main(){

    int a =10;
    void(*block)(void) =  &__main_block_impl_0 (

              __main_block_func_0,
                                                
              &__main_block_desc_0_DATA,
                                                
              a
        ) ;
     
     ((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
    return 0;
}
  • 此時(shí)我們看到 當(dāng)block內(nèi)部引用到了外部變量的時(shí)候漓雅。__main_block_impl_0 構(gòu)造函數(shù)就會(huì)動(dòng)態(tài)的向后添加一個(gè) 參數(shù)。

再次看下__main_block_impl_0結(jié)構(gòu)體變化

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 可以看到 __main_block_impl_0結(jié)構(gòu)體中多了一個(gè)int 類(lèi)型的 a. 通過(guò)構(gòu)造函數(shù) 將a賦值。

再次 看 __block_implFuncPtr 賦值 也就是外界傳進(jìn)來(lái)的 __main_block_func_0 函數(shù)實(shí)現(xiàn)

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy


        printf("LG_Cooci%d",a);
    }
  • 此時(shí)我們可以看到 當(dāng)block發(fā)起調(diào)用的時(shí)候 此時(shí) 將 __main_block_func_0結(jié)構(gòu)體中的a的值 賦值給了一個(gè)臨時(shí)變量 邻吞。

  • 由此就可以下結(jié)論组题,此時(shí)是值拷貝,外界a的變化 并不會(huì) 引起 block內(nèi)部 a的變化抱冷。

為了徹底弄清楚 我們 寫(xiě)一個(gè)我們平常的oc 對(duì)象,在block塊內(nèi)部引用

請(qǐng)問(wèn)下面輸出什么崔列?

      LGPerson * person = [[LGPerson alloc]init];
      person.tag = @"等風(fēng)來(lái)不如追風(fēng)去,總有那么一個(gè)人在風(fēng)景正好的季節(jié)來(lái)到你的身邊";
     
       void(^block)(void) = ^{
 
            NSLog(@"%@",person.tag);
            
        };
 
        person.tag = @"45°仰望天空旺遮,該死我那無(wú)處安放的魅力";
 
        block();

我們 clang 去看


        LGPerson * person = (((void *)objc_msgSend)((id)((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc")), sel_registerName("init"));


        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setTag:"), (NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_6caa76_mi_0);
                             
   ///block 構(gòu)造函數(shù) 結(jié)構(gòu)體賦值
        void(*block)(void) = &__main_block_impl_0(
                                                  __main_block_func_0,
                                                  
                                                  &__main_block_desc_0_DATA,
                                                  
                                                  person,
                                                  
                                                  570425344));

        
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setTag:"), (NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_6caa76_mi_2);

        ///發(fā)起調(diào)用
         ((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        
  • 此時(shí)我們看到 此時(shí)__main_block_impl_0person指針捕獲進(jìn)去了赵讯。

再次 看此時(shí)的 __main_block_impl_0結(jié)構(gòu)體

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  LGPerson *person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,LGPerson *_person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 可以看到結(jié)構(gòu)體內(nèi)部已經(jīng)多了一個(gè) 對(duì)象指針。

在看一下方法

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  LHPerson *person = __cself->person; // bound by copy


            NSLog((NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_6caa76_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("tag")));

          }
  • 當(dāng)block 發(fā)起調(diào)用的時(shí)候耿眉,首先找到 impl中存入的 方法边翼,并用此方法發(fā)起調(diào)用,并將 block的結(jié)構(gòu)體對(duì)象 當(dāng)參數(shù)鸣剪,傳入组底。此時(shí)可以看到 獲取了 block結(jié)構(gòu)體中的 person指針。
  • 到現(xiàn)在我們明白了 此時(shí)對(duì)象類(lèi)型的捕獲的是對(duì)象的指針筐骇。屬于指針copy债鸡。也就是淺拷貝。

值copy 此時(shí) 內(nèi)存空間 兩個(gè)一樣的 內(nèi)容铛纬。指針 不一樣厌均。也就是深拷貝。
指針 copy 此時(shí) copy了一個(gè)指針饺鹃,兩個(gè)指針指向同一片內(nèi)存區(qū)域莫秆。也就是淺拷貝。

我們對(duì)于 值拷貝的基礎(chǔ)數(shù)據(jù)類(lèi)型的捕獲 該如何操作呢悔详?

__block

在什么情況下我們需要用__block的修飾镊屎?

  • 當(dāng)block內(nèi)部需要對(duì)外界的變量 修改時(shí),如不用__block修飾茄螃,會(huì)引起編譯器的歧義缝驳,導(dǎo)致只能讀。
  • 當(dāng)捕獲的是臨時(shí)變量归苍,如不用__block修飾用狱,會(huì)導(dǎo)致內(nèi)外數(shù)據(jù)不同步。
  • 如捕獲的是容器類(lèi)型拼弃,容器內(nèi)容發(fā)生更改不需要進(jìn)行__block修飾夏伊。
  • 如捕獲的是對(duì)象,對(duì)象的某個(gè)屬性發(fā)生更改吻氧,不需要進(jìn)行__block修飾溺忧。
  • 如捕獲的是 statc修飾的(局部 /全局)變量 或 全局變量 不需要__block修飾咏连。

__block又做了哪些事情?帶著疑問(wèn)向下分析

int main(){
      
    __block int a =10;
    void(^block)(void) = ^{
    
        printf("LG_Cooci%d",a);
    };
    
    a = 20;
    block();
    return 0;
}

繼續(xù) clang看編譯期變成了什么樣

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};
 ===============================================

    __Block_byref_a_0 a = {
              0,
              (__Block_byref_a_0 *) &a,
              0,
              sizeof(__Block_byref_a_0),
              10
             
         };

  void(*block)(void) =  &__main_block_impl_0 (

                          __main_block_func_0,
                                                   
                          &__main_block_desc_0_DATA,
                                                   
                          (__Block_byref_a_0 *)&a,
                                                   
                          570425344
               );

        (a.__forwarding->a) = 20;
       ((__block_impl *)block)->FuncPtr)((__block_impl *)block);




  • 此時(shí)我們看到了變量 a 被包裝成了 一個(gè) __Block_byref_a_0類(lèi)型的結(jié)構(gòu)體對(duì)象,并相對(duì)應(yīng)的錄入變量a的信息鲁森, 對(duì)應(yīng)上面結(jié)構(gòu)體可以清楚的看到 里面存有a的地址賦值給__forwarding指針祟滴,a的值自身大小 等參數(shù)歌溉。

  • 將這個(gè)包裝后的a的結(jié)構(gòu)體對(duì)象取地址 垄懂, 作為block結(jié)構(gòu)體的構(gòu)造函數(shù) __main_block_impl_0 參數(shù)傳入 賦值給 block結(jié)構(gòu)體里邊的 a指針

  • 調(diào)用執(zhí)行上一行代碼 拿到a結(jié)構(gòu)體指針修改 a變量的值痛垛。所以內(nèi)外同步數(shù)據(jù)草慧。
    繼續(xù)查看 block結(jié)構(gòu)體 __main_block_impl_0

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

查看 __main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref


            printf("LG_Cooci%d",(a->__forwarding->a));
        }
  • 這里清晰的看到獲取到block結(jié)構(gòu)體里面的包裝后的__Block_byref_a_0類(lèi)型的 a指針,并通過(guò) a指針拿到 __forwarding 也就是 指向外界變量的a地址的指針匙头,并取出 變量a真正的值冠蒋。
  • 這里從不加__Block的值拷貝 變成了 指針拷貝。而這個(gè)指針是指向的同 一個(gè)結(jié)構(gòu)體地址乾胶,這個(gè)結(jié)構(gòu)體里面存有 變量 a的地址 和a的值,

咦朽寞?那為啥數(shù)據(jù)就同步了呢识窿,我不用__Block修飾 我捕獲一個(gè)字符串,它也是指針那為啥 當(dāng)我在對(duì)block發(fā)起調(diào)用前重新修改 字符串的值脑融,它怎么數(shù)據(jù)不同步呢喻频?

    NSString * str = [NSString stringWithFormat:@"等風(fēng)來(lái)不如追風(fēng)去啊"];
           
           void (^block)(void) = ^{
             
               NSLog(@"%@,%p",str,str);
           };
         
           str = @"總有一個(gè)人,在風(fēng)景正好的季節(jié)等著你";
           NSLog(@"%@,%p",str,str);

        
           block();
        
  • 看著上面的疑問(wèn) 在次陷入深思,我們繼續(xù)看下clang之后的編譯期代碼
    ///字符串指針
         NSString * str = ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull __strong, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_e485f6_mi_0);
        
          /// block
           void (*block)(void) =
        __main_block_impl_0(
                            __main_block_func_0,
                            
                            &__main_block_desc_0_DATA,
                            
                            str,
                            
                            570425344
                            );

         ///重新賦值 改變指針指向
         str = (NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_e485f6_mi_2;
          
        /// 打印
         NSLog((NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_e485f6_mi_3,str,str);

        ///發(fā)起調(diào)用
         ((__block_impl *)block)->FuncPtr)((__block_impl *)block);

看func函數(shù)

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSString *__strong str = __cself->str; // bound by copy


               NSLog((NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_e485f6_mi_1,str,str);
           }
    }
  • 看到這里的的確確是指針拷貝肘迎,func函數(shù)里的指針指向 和 block析構(gòu)函數(shù)參數(shù)指針指向同一片地址空間甥温。
  • 那為啥 我在調(diào)用之前更改了 str變量的值里邊它里邊不同步?

繼續(xù)帶著這個(gè)疑問(wèn) 我們打印一下上下str的指針指向地址妓布。


截屏2020-12-11 下午3.53.57.png
  • 雖然是捕獲的是指針姻蚓,在調(diào)用之前 指針的指向被改變,它指向了新的一片地址空間 匣沼。

__block修飾 運(yùn)行

截屏2020-12-11 下午3.58.58.png

  • 此時(shí)我們知道 str被封裝為一個(gè)結(jié)構(gòu)體對(duì)象
  • 而在調(diào)用block之前進(jìn)行進(jìn)行對(duì) str修改狰挡,此時(shí)為結(jié)構(gòu)體指針copy。 對(duì)它指向的這個(gè)結(jié)構(gòu)體地址里的 str的值所占用的內(nèi)存空間進(jìn)行了修改释涛。所以數(shù)據(jù)同步加叁。

為了驗(yàn)證我們的想法 再次查看用block修飾后的cpp


struct __Block_byref_str_0 {
  void *__isa;
__Block_byref_str_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *__strong str;
===============================================


 ///byref結(jié)構(gòu)體對(duì)象
         __Block_byref_str_0 str = {
             (void*)0,
             
             (__Block_byref_str_0 *)&str,
             
             33554432,
             
             sizeof(__Block_byref_str_0),
             
             
             __Block_byref_id_object_copy_131,
             
             __Block_byref_id_object_dispose_131,
             
             ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull __strong, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_1c411f_mi_0)
             
         };

        
        /// block
          &__main_block_impl_0(
                               __main_block_func_0,
                               
                               &__main_block_desc_0_DATA,
                               
                               (__Block_byref_str_0 *)&str,
                               
                               570425344
                               );

        ///重新賦值
        (str.__forwarding->str) = (NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_1c411f_mi_2;
          
        ///打印
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_1c411f_mi_3,(str.__forwarding->str),(str.__forwarding->str));

        ///函數(shù)調(diào)用
        (__block_impl *)block)->FuncPtr)((__block_impl *)block);
        
  • 咦這里好像和基本數(shù)據(jù)類(lèi)型 int a的包裝還不太一樣多了兩個(gè)函數(shù) __Block_byref_id_object_copy 和__Block_byref_id_object_dispose
  • 我們重新賦值是改變的 包裝后的結(jié)構(gòu)體中的 str指針。

在看一下 func

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_str_0 *str = __cself->str; // bound by ref


               NSLog((NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_1c411f_mi_1,(str->__forwarding->str),(str->__forwarding->str));
           }
  • 從這里我們就能看出 此時(shí)是byref結(jié)構(gòu)體指針copy 通過(guò)指針獲取 forwarding 地址在取得 str指針指向唇撬。

總結(jié)

  • 捕獲的外界變量 底層會(huì)包裝成一個(gè) __Block_byref_a_0類(lèi)型的結(jié)構(gòu)體它匕。
  • 結(jié)構(gòu)體用來(lái)保存 原始的變量的指針 和值。
  • 將包裝的成的結(jié)構(gòu)體對(duì)象地址 傳遞 給block ,然后block內(nèi)部就可以對(duì)外界變量進(jìn)行操作窖认。
  • 但是其內(nèi)部是到底是怎么操作的為什么 string對(duì)象類(lèi)型 要比 int基本數(shù)據(jù)類(lèi)型 byref會(huì)多出兩個(gè)方法豫柬?帶著這些疑問(wèn)向下看告希。

block真正的類(lèi)型

打開(kāi)匯編,并在下面區(qū)域打上斷點(diǎn)


截屏2020-11-28 下午5.58.06.png

運(yùn)行


截屏2020-12-02 下午6.10.52.png

我們看到到了callq 了 幾個(gè)很重要的函數(shù) 一個(gè)

  • objc_retainBlock
  • objc_storeStrong
  • _Block_object_dispose

分別符號(hào)斷點(diǎn)下這個(gè) 看他來(lái)自哪個(gè)"星球"
斷點(diǎn) objc_retainBlock

截屏2020-12-02 下午6.15.24.png

  • 看到重要線索 它來(lái)自 libobjc, 并其實(shí)真正調(diào)用的是 _Block_copy;
  • 那還等什么去源碼看看轮傍。

objc4源碼全局搜索 objc_retainBlock

id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}

  • 嗯沒(méi)毛病 的確調(diào)用的 是 _Block_copy;

全局搜索 _Block_copy發(fā)現(xiàn)Objc并未發(fā)現(xiàn)什么

那接著下符號(hào)斷點(diǎn)吧它肯定不來(lái)自這個(gè)庫(kù)了暂雹。


截屏2020-12-02 下午6.23.02.png
  • 原來(lái)它來(lái)自 libsystem_blocks.dylib。

官網(wǎng)找到開(kāi)源庫(kù)全局搜索 _Block_copy

// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
void *_Block_copy(const void *arg) {
    
    // block都是`Block_layout`類(lèi)型
    struct Block_layout *aBlock;

    // 沒(méi)有內(nèi)容创夜,直接返回空
    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    // 將內(nèi)容轉(zhuǎn)變?yōu)閌Block_layout`結(jié)構(gòu)體格式
    aBlock = (struct Block_layout *)arg;
    // 檢查是否需要釋放
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 如果是全局Block,直接返回
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    //
    else {
        // Its a stack block.  Make a copy.
        // 進(jìn)入的是棧區(qū)block杭跪,拷貝一份
        // 開(kāi)辟一個(gè)大小空間的result對(duì)象
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        // 開(kāi)辟失敗,就返回
        if (!result) return NULL;
        // 內(nèi)存拷貝:將aBlock內(nèi)容拷貝到result中
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        //result的invoke指向aBlock的invoke驰吓。
        result->invoke = aBlock->invoke;
#endif
        // reset refcount
        // BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING :前16位都為1
        // ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING):前16位都為0
        // 與操作涧尿,結(jié)果為前16位都為0 引用計(jì)數(shù)為0
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        // 設(shè)置為需要釋放,引用計(jì)數(shù)為1
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        // 生成desc檬贰,并記錄了result和aBlock
        _Block_call_copy_helper(result, aBlock); //
        // Set isa last so memory analysis tools see a fully-initialized object.
        // 設(shè)置isa為堆區(qū)Block
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}
  • 這里我們看到了 block真正的類(lèi)型 原來(lái)它是 Block_layout 類(lèi)型的結(jié)構(gòu)體
  • 仔細(xì)看 上面源代碼的幾個(gè) if else 判斷
    1姑廉、如果需要釋放的(堆是由程序員管理的) 也就是 堆block的,增加引用計(jì)數(shù) 返回
    2翁涤、如果是全局的桥言,直接返回
    3、如果是棧block.:從棧中 copy到 堆中葵礼; 過(guò)程: malloc開(kāi)辟空間 ->memmove內(nèi)存拷貝 ->invoke 指針拷貝->flag引用計(jì)數(shù) 設(shè)置為1 ->生成desc ->設(shè)置isa為堆block ->返回堆block.

查看 Block_layout

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor; //
    // imported variables
};
  • isa : 從靜態(tài)分析 到 動(dòng)態(tài)庫(kù)我們都知道了号阿,它就是標(biāo)記為是什么類(lèi)型的block。
  • flags: 標(biāo)識(shí)碼(每一位都有特殊含義)
  • reserved : 保留字段
  • invoke : block執(zhí)行函數(shù)(存儲(chǔ)執(zhí)行代碼塊)
  • descriptor: Block詳細(xì)信息

查看 Flags:標(biāo)識(shí)碼

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};
  • flags的賦值鸳粉。按bit位表示一些block的附加信息扔涧,類(lèi)似 isa中的位域,其中flags的種類(lèi)有上面幾種

查看 Block_descriptor_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
};

  • 這里我們看到了 這個(gè) 類(lèi)型擁3個(gè)結(jié)構(gòu)體樣式届谈。
  • 可選類(lèi)型 descriptor 2 為BLOCK_HAS_COPY_DISPOSE
  • 可選類(lèi)型 descriptor 3 為BLOCK_HAS_SIGNATURE

總結(jié)

  • block真正的底層結(jié)構(gòu)為block_layout, 它里面包含 isa 枯夜,isa為最終確定的類(lèi)型。還有flag 艰山, 類(lèi)似 isa中的位域 湖雹。它里面記錄著當(dāng)前block的狀態(tài),如是否需要釋放曙搬,是否是global 劝枣,是否需要簽名進(jìn)行消息發(fā)送等。運(yùn)行時(shí)會(huì)調(diào)用block_copy织鲸,通過(guò)編譯期的flag判斷當(dāng)前block的類(lèi)型舔腾,如果是 需要釋放的 操作引用計(jì)數(shù)并返回,如是全局block不做任何操作返回搂擦,如果是棧區(qū)的block 需要將 棧區(qū)的block Copy 到堆上稳诚,(申請(qǐng)內(nèi)存空間 ,將棧區(qū)的block拷貝的堆區(qū) 瀑踢,將 block的執(zhí)行函數(shù) invoke拷貝扳还,重新設(shè)置 flages 類(lèi)型才避,生成對(duì)應(yīng)的 desc,設(shè)置 isa類(lèi)型為堆block) 此時(shí)block為最真實(shí)的狀態(tài)。

查看 _Block_call_copy_helper

static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
    struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
    if (!desc) return;

    (*desc->copy)(result, aBlock); // do fixup
}
  • 這里可以看到 如果擁有拓展descriptor2那么會(huì)發(fā)起一個(gè)函數(shù)調(diào)用

查看descriptor訪問(wèn)操作

#if 0
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
    return aBlock->descriptor;
}
#endif

static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}

static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
        desc += sizeof(struct Block_descriptor_2);
    }
    return (struct Block_descriptor_3 *)desc;
}

  • 這里可以清晰的看到氨距,默認(rèn)獲取 block_layout 里 descriptor信息
  • 根據(jù) block_layout里的flags& BLOCK_HAS_COPY_DISPOSE 如果為真 證明 有descriptor_2附加信息桑逝。 拿到 descripor1的指針 平移自身大小 得到 descriptor_2。
  • 根據(jù) block_layout里的flags& BLOCK_HAS_SIGNATURE 如果為真 證明 有descriptor_3附加結(jié)構(gòu)體信息俏让。首先拿到 拿到 descripor1的指針 平移其自身大小 ,并查看是否有descriptor_2附加結(jié)構(gòu)體楞遏,如果有,那么在平移加上 decriptor2大小 ,最終得到 descriptor_3

看到這里我們應(yīng)該更能體會(huì)到 descriptor 屬性及上面的附加可選什么意思 下面畫(huà)個(gè)圖


通過(guò)指針平移獲取desc

以上為我們開(kāi)了上帝視角 下面我們實(shí)際操作 親眼所看到 從棧block 拷貝到堆的過(guò)程

上面我們已經(jīng)通過(guò)閱讀源碼知道了 當(dāng)?shù)讓诱{(diào)用完Block_copy 其真實(shí)的block類(lèi)型就會(huì)確定所以我們?cè)谡{(diào)用之前打斷點(diǎn)讀取

截屏2020-12-11 下午2.05.42.png
  • 可以看到此時(shí)為 NSStackBlock

按住 ctrl + 鼠標(biāo)點(diǎn)擊 向下箭頭 首昔,跳進(jìn) objc_retainBlock 方法繼續(xù)打印


截屏2020-12-11 下午1.28.59.png

跳進(jìn)了 objc_retainBlock


截屏2020-12-11 下午2.06.44.png
  • 此時(shí)可以看到 依舊為NSStackBlock 地址指針并沒(méi)有變化

打入objc_retainBlock的全局?jǐn)帱c(diǎn) 并繼續(xù)讀取


截屏2020-12-11 下午2.08.42.png
  • 可以看到依舊沒(méi)有變化

按住 ctrl + 鼠標(biāo)點(diǎn)擊 向下箭頭 繼續(xù)向下走


截屏2020-12-11 下午2.12.20.png
  • 此時(shí)可以看到清晰的它調(diào)用 libobjc庫(kù)的 objc_retainBlock方法
  • 此時(shí) 依舊沒(méi)有變化

繼續(xù)跟進(jìn)跳轉(zhuǎn)


截屏2020-12-11 下午2.14.45.png
  • 發(fā)現(xiàn)太長(zhǎng)了 那么這里我們只需要斷到其 返回值
截屏2020-12-11 下午2.16.19.png
  • 此時(shí)此刻 它發(fā)生了變化寡喝。變成了 NSMallocBlock
  • 這也就很清晰的看到了block是什么時(shí)候從棧block變?yōu)槎训摹?/li>

我們分析了block是如何確定最終類(lèi)型的,那還是不了解block是如何捕獲外界變量的勒奇,為什么__block修飾后 數(shù)據(jù)會(huì)同步呢预鬓? 下面我繼續(xù)分析 底層

先看圖


__block clang.jpg
  • 首先經(jīng)過(guò)這兩種類(lèi)型的__block我們發(fā)現(xiàn) 不同的地方就是修飾指針類(lèi)型的對(duì)象在byref包裝結(jié)構(gòu)體中會(huì)多出兩個(gè)函數(shù)。
  • 共同地方是經(jīng)過(guò)__block修飾后在block_impl中的desc結(jié)構(gòu)體會(huì)多出兩個(gè)函數(shù)赊颠。
    看到這里我們也許就更加明白了格二,還記的blockLayout結(jié)構(gòu)體中desc嗎?它的desc有可選的拓展結(jié)構(gòu)體竣蹦,是根據(jù) blockLayout里的flags&上 枚舉來(lái)確定是否擁有蟋定,在這里用__block修飾之后,它多出的這兩個(gè)函數(shù)正好和descriptor_2一一對(duì)應(yīng)草添。
  • 他們底層調(diào)用的同屬 _Block_object_assign 和 _Block_object_dispose函數(shù)

源碼搜索 _Block_object_assign

void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
   
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
     
        case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/
        // objc 指針地址 weakSelf (self)
            // arc
        _Block_retain_object(object);
            // 持有
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/
            
            // block 被一個(gè) block 捕獲

        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/
            
        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      default:
        break;
    }
}

  • 如果是普通對(duì)象,交給系統(tǒng)arc處理扼仲,并拷貝對(duì)象指針远寸,引用計(jì)數(shù)+1 ,外界變量不能釋放屠凶。
  • 如果是block類(lèi)型的變量驰后,又會(huì)回到_Block_copy操作,將block從棧 拷貝到堆區(qū)矗愧。
  • 如果是__block修飾的變量灶芝,調(diào)用_Block_byref_copy函數(shù),進(jìn)行內(nèi)存拷貝及處理唉韭。

查看 枚舉 值

 
// Runtime support functions used by compiler when generating copy/dispose helpers

// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
    // see function implementation for a more complete description of these fields and combinations
    //普通對(duì)象夜涕,即沒(méi)有其他的引用類(lèi)型
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    //block類(lèi)型作為變量
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    //經(jīng)過(guò)__block修飾的變量
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    //weak 弱引用變量
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    //返回的調(diào)用對(duì)象 - 處理block_byref內(nèi)部對(duì)象內(nèi)存會(huì)加的一個(gè)額外標(biāo)記,配合flags一起使用
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};

搜索 _Block_byref_copy

static struct Block_byref *_Block_byref_copy(const void *arg) {
    
    //強(qiáng)轉(zhuǎn)為Block_byref結(jié)構(gòu)體類(lèi)型属愤,保存一份
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack 申請(qǐng)內(nèi)存
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        //block內(nèi)部持有的Block_byref 和 外界的Block_byref 所持有的對(duì)象是同一個(gè)女器,這也是為什么__block修飾的變量具有修改能力
        //copy 和 scr 的地址指針達(dá)到了完美的同一份拷貝,目前只有持有能力
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
        //如果有copy能力
        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            //Block_byref_2是結(jié)構(gòu)體住诸,__block修飾的可能是對(duì)象驾胆,對(duì)象通過(guò)byref_keep保存涣澡,在合適的時(shí)機(jī)進(jìn)行調(diào)用
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }
            //等價(jià)于 __Block_byref_id_object_copy
            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}
  • 可以看到被__block包裝的變量,真實(shí)的類(lèi)型為Block_byref結(jié)構(gòu)體丧诺。
  • 將棧上的Block_byref 結(jié)構(gòu)體拷貝到堆上入桂,根據(jù)大小申請(qǐng)內(nèi)存空間--> 設(shè)置isa為 Null ->設(shè)置flags信息
    -> 設(shè)置堆上Block_byref結(jié)構(gòu)體的forwarding指針指向 為 自己->更改棧上Block_byref結(jié)構(gòu)體的forwarding 指針指向?yàn)槎焉系腂lock_byref ->設(shè)置堆byref的size大小 為 棧上的byref的size大小。
  • 判斷如果有copy dispose,(這里我們?cè)谏厦嬲f(shuō)過(guò)驳阎,__block修飾的指針類(lèi)型抗愁,比基本數(shù)據(jù)類(lèi)型在包裝的結(jié)構(gòu)體中會(huì)多出來(lái)兩個(gè)函數(shù),此時(shí)和這里是一一對(duì)應(yīng)的),通過(guò)類(lèi)似上面獲取desc2 和desc3的方式搞隐,這里是偏移一個(gè)Block_byref 大小 拿到 src2也就是包含copy和dispose成員變量的Block_byref_2結(jié)構(gòu)體,來(lái)獲取 copy和dispose 函數(shù)并將其拷貝到堆中驹愚。 判斷如果有 layout成員變量,與獲取src2一樣的效果劣纲,這里是偏移一個(gè)Block_byref_2的大小來(lái)獲取src3 并將layout變量拷貝到堆上逢捺,也就是堆上Block_byref_3 的變量layout 指向棧中l(wèi)ayout。通過(guò)調(diào)用 byref_keep來(lái)實(shí)現(xiàn)響應(yīng)癞季,它就對(duì)應(yīng)外部的__Block_byref_id_object_copy

我們看一下 Block_byref 結(jié)構(gòu)體

struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep; // 結(jié)構(gòu)體 __block  對(duì)象
    BlockByrefDestroyFunction byref_destroy;
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};

在看一下 byref中的flags的枚舉

// Values for Block_byref->flags to describe __block variables
enum {
    // Byref refcount must use the same bits as Block_layout's refcount.
    // BLOCK_DEALLOCATING =      (0x0001),  // runtime
    // BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime

    BLOCK_BYREF_LAYOUT_MASK =       (0xf << 28), // compiler
    BLOCK_BYREF_LAYOUT_EXTENDED =   (  1 << 28), // compiler
    BLOCK_BYREF_LAYOUT_NON_OBJECT = (  2 << 28), // compiler
    BLOCK_BYREF_LAYOUT_STRONG =     (  3 << 28), // compiler
    BLOCK_BYREF_LAYOUT_WEAK =       (  4 << 28), // compiler
    BLOCK_BYREF_LAYOUT_UNRETAINED = (  5 << 28), // compiler

    BLOCK_BYREF_IS_GC =             (  1 << 27), // runtime

    BLOCK_BYREF_HAS_COPY_DISPOSE =  (  1 << 25), // compiler
    BLOCK_BYREF_NEEDS_FREE =        (  1 << 24), // runtime
};

  • 此時(shí)可以看出 和Block_copy中的處理方式非常的相似

在_Block_byref_copy中我們看到src2->byref_keep劫瞳,其實(shí)就是調(diào)用外部的__Block_byref_id_object_copy_131,為什么绷柒?
這里我們 看 Block_byref_2 中兩個(gè)函數(shù) 志于,clang編譯器中的兩個(gè)函數(shù)

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep; // 結(jié)構(gòu)體 __block  對(duì)象
    BlockByrefDestroyFunction byref_destroy;
};

截屏2020-12-14 上午11.24.26.png
截屏2020-12-14 上午11.22.55.png
  • __Block_byref_id_object_copy_131入?yún)⒗锩妫幸粋€(gè)內(nèi)存平移40废睦,

原因


截屏2020-12-14 上午11.31.57.png
  • 因?yàn)?內(nèi)存偏移 40才能取到 NSstring*__strong str

而131 = 128 +3伺绽,其中128表示BLOCK_BYREF_CALLER --> 代表__block變量有copy/dispose的內(nèi)存管理輔助函數(shù)

截屏2020-12-14 上午11.35.20.png

我們這里示例的對(duì)象類(lèi)型為NSString,就表示上述枚舉中這個(gè) BLOCK_FIELD_IS_OBJECT,也就是繼承NSObjcet類(lèi)型的 id類(lèi)型的 為3嗜湃,然后和copy函數(shù)拼接起來(lái)就是 __Block_byref_id_object_copy_131

所以在_Block_byref_copy 中以下標(biāo)紅出就相當(dāng)于 __Block_byref_id_object_copy_131的調(diào)用


截屏2020-12-14 上午11.39.39.png

而這里的調(diào)用又會(huì)觸發(fā) _Block_object_assign

截屏2020-12-14 上午11.45.54.png

總結(jié)

詳細(xì)總結(jié):

Block真正的底層是Block_layout 對(duì)象奈应,clang編譯器 會(huì)根據(jù)捕獲類(lèi)型,來(lái)動(dòng)態(tài)的改變购披,及生成對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)杖挣。如用__block修飾后的對(duì)象,clang編譯器會(huì)將其封裝為一個(gè)byref的結(jié)構(gòu)體對(duì)象刚陡,此結(jié)構(gòu)體對(duì)象在底層真正的類(lèi)型為 Block_byref 結(jié)構(gòu)體惩妇。
在運(yùn)行時(shí) 會(huì)調(diào)用Block_copy 函數(shù) 通過(guò) block_layout對(duì)象中的flags標(biāo)記 判斷當(dāng)前block的類(lèi)型及狀態(tài)。如果是需要釋放的 那么 只操作引用計(jì)數(shù)并返回筐乳,如果是全局block那么直接返回歌殃,如果是棧區(qū)的block, 開(kāi)辟內(nèi)存空間 蝙云,設(shè)置屬性為堆區(qū)的標(biāo)識(shí)及一些設(shè)置挺份。其中最具代表性的屬性為 desc ,在默認(rèn)情況下block的描述desc只有一個(gè)贮懈,當(dāng)被__block修飾之后 匀泊,clang編譯器會(huì)在desc結(jié)構(gòu)體中多出兩個(gè)函數(shù)copy/dispose 底層會(huì)根據(jù) block的flags 標(biāo)識(shí) 來(lái)判斷是否擁有 desc2 或者 desc3 的block的拓展信息优训,如判斷擁有 copy/dispose 函數(shù),那么會(huì)執(zhí)行copy函數(shù)此時(shí)會(huì)調(diào)用Block_object_assign函數(shù) 此函數(shù)中同樣的會(huì)判斷當(dāng)前捕獲的是什么類(lèi)型各聘,進(jìn)行不同的處理揣非, 此時(shí)是__block修飾的變量也就byref結(jié)構(gòu)體 將會(huì)掉起 _Block_byref_copy 函數(shù),此函數(shù)正是對(duì)byref結(jié)構(gòu)體 從棧中copy到堆中的操作躲因, 類(lèi)似block的copy早敬。首先開(kāi)辟內(nèi)存,設(shè)置 屬性為堆區(qū)的標(biāo)識(shí)及一些設(shè)置大脉,這里重要的操作為搞监,將堆區(qū)的forwarding指針 指向 堆區(qū)的Block_byref自己.將棧區(qū)的forwarding指針指向更改為堆區(qū)的Block_byref結(jié)構(gòu)體。并設(shè)置 棧區(qū)的大小镰矿。同樣根據(jù)棧區(qū)的byref標(biāo)識(shí)flags判斷是否支持 copy/和dispose函數(shù)琐驴,如果支持,通過(guò)指針平移獲取棧區(qū)堆區(qū)的 Block_byref2 拓展結(jié)構(gòu)體, 從棧區(qū)的這兩個(gè)函數(shù)指針賦值 堆區(qū)的 Block_byref2 中秤标。再此判斷中還判斷了是否支持layout拓展绝淡,如支持 同樣通過(guò)指針平移獲取棧區(qū)堆區(qū)的 Block_byref3拓展結(jié)構(gòu)體,從棧區(qū)的這個(gè)函數(shù)指針賦值 堆區(qū)的 Block_byref3 中.
如支持copy/dispose 函數(shù) 那么將再次發(fā)起 Block_object_assign函數(shù)調(diào)用苍姜,此時(shí)進(jìn)行的是通過(guò)Block_byref結(jié)構(gòu)體偏移獲取被修飾的指針變量進(jìn)行 指針copy 也就是引用計(jì)數(shù)+1

非太詳細(xì):

也就是 __block修飾的基本數(shù)據(jù)類(lèi)型會(huì)進(jìn)行 二次copy 一個(gè)是block的copy 一個(gè)是byref結(jié)構(gòu)體的copy 都是從 棧中 copy到堆中牢酵。

如果修飾的是指針類(lèi)型,那么會(huì)進(jìn)行三次 copy衙猪,前兩次和上面一樣馍乙,最后一次 會(huì)對(duì)修飾的原始指針,進(jìn)行 指針copy引用計(jì)數(shù)+1.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末垫释,一起剝皮案震驚了整個(gè)濱河市丝格,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌饶号,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件季蚂,死亡現(xiàn)場(chǎng)離奇詭異茫船,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)扭屁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)算谈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人料滥,你說(shuō)我怎么就攤上這事然眼。” “怎么了葵腹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵高每,是天一觀的道長(zhǎng)屿岂。 經(jīng)常有香客問(wèn)我,道長(zhǎng)鲸匿,這世上最難降的妖魔是什么爷怀? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮带欢,結(jié)果婚禮上运授,老公的妹妹穿的比我還像新娘。我一直安慰自己乔煞,他們只是感情好吁朦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著渡贾,像睡著了一般逗宜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上剥啤,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天锦溪,我揣著相機(jī)與錄音,去河邊找鬼府怯。 笑死刻诊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的牺丙。 我是一名探鬼主播则涯,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼冲簿!你這毒婦竟也來(lái)了粟判?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤峦剔,失蹤者是張志新(化名)和其女友劉穎档礁,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體吝沫,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呻澜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了惨险。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片羹幸。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖辫愉,靈堂內(nèi)的尸體忽然破棺而出栅受,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布屏镊,位于F島的核電站依疼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏闸衫。R本人自食惡果不足惜涛贯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蔚出。 院中可真熱鬧弟翘,春花似錦、人聲如沸骄酗。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)趋翻。三九已至睛琳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間踏烙,已是汗流浹背师骗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留讨惩,地道東北人辟癌。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像荐捻,于是被迫代替她去往敵國(guó)和親黍少。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

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

  • Block是iOS開(kāi)發(fā)中一種比較特殊的數(shù)據(jù)結(jié)構(gòu)处面,它可以保存一段代碼厂置,在合適的地方再調(diào)用,具有語(yǔ)法簡(jiǎn)介魂角、回調(diào)方便昵济、編...
    飛魚(yú)灣閱讀 4,121評(píng)論 0 7
  • 基本使用 block常見(jiàn)的使用方式如下: Block的本質(zhì) - OC對(duì)象 結(jié)論: block的內(nèi)部存在isa指針,...
    青龍翱翔閱讀 308評(píng)論 0 0
  • 目錄 Block底層解析什么是block野揪?block編譯轉(zhuǎn)換結(jié)構(gòu)block實(shí)際結(jié)構(gòu)block的類(lèi)型NSConcre...
    tripleCC閱讀 33,186評(píng)論 32 388
  • 關(guān)于我的倉(cāng)庫(kù) 這篇文章是我為面試準(zhǔn)備的iOS基礎(chǔ)知識(shí)學(xué)習(xí)中的一篇 我將準(zhǔn)備面試中找到的所有學(xué)習(xí)資料访忿,寫(xiě)的Demo,...
    太陽(yáng)騎士索拉爾閱讀 626評(píng)論 0 5
  • iOS Objective-C Block底層原理 在上一篇文章中我們對(duì)Block做了簡(jiǎn)單的介紹囱挑,下面我們通過(guò)這篇...
    just東東閱讀 377評(píng)論 0 2