block详细了解及底层探索

2020-12-14  本文已影响0人  猿人

block 三种类型

全局block NSGlobalBlock
   void (^block)(void) = ^{
        NSLog(@"hahah");
    };
    
    NSLog(@"%@", block);
堆区block NSMallocBlock
   int a = 10;
    void (^block)(void) = ^{
        NSLog(@"Cooci - %d",a);
    };

    NSLog(@"%@",block);
栈区block NSStackBlock

这里有个坑点,在iOS14之前 在block没有进行copy处理的时候它是一个栈区block,而之后却放在了堆里。

  NSLog(@"%@",^{
        NSLog(@"Cooci - %d",a);
    });
 

栈区的block写法

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

    NSLog(@"%@",block);

block 循环引用

正常释放

当 A 对象 持有 B对象 的时候,B对象 的引用计数 会+1


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

当A释放的时候会给 B 信号,B接收到 release信号,引用计数 -1 等于0的时候 b的dealloc就会被调用


截屏2021-07-06 下午3.52.21.png
循环引用

当 A 持有 B ,B也持有 A ,你中有我 我中有你的情况。就会造成循环引用。


截屏2021-07-06 下午3.59.49.png
循环引用代码示意
   ///会发生循环引用
   self.block = ^(void){
         NSLog(@"%@",self.name);
    };
 ///不会发生循环引用
 [UIView animateWithDuration:0.2 animations:^{
        NSLog(@"%@",self.name);
    }];
解决打破循环引用。

1、__weak typeof(self)weakSelf = self

__weak  typeof(self)weakSelf = self
   self.block = ^(void){
        NSLog(@"%@", weakSelf.name);
   };

那这样就没问题了吗看下面

   __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();

此时我们发现 当前页面确实走了 dealloc。但是 当 延时任务回来的时候 ,却发现 打印的为nil. 虽说一个打印任务并无商大雅。但是当里面执行的任务为很重要的时候。我还没走完你就 dealloc,显然不符合我的要求。所以我们正确的用法为 weak - strong -Dance 强弱共舞,保证self的声明周期。

__weak typeof(self) weakSelf = self;
   self.block = ^(void){
        // 时间 - 精力
        // 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();

2、通过传参的形式将self 传进block任务中。

    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);

3、主动打破循环

   __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();

4、NSProxy 也可以,这里就不讲了,自行搜索。

底层探究

定义一个简单的.c文件 如下 ;

int main(){
      void(^block)(void) = ^{
    
        printf("LG_Cooci");
    };
     //block();
    return 0;
}
int main(){
     ///简化去掉返回值类型
    void(*block)(void) =  
&__main_block_impl_0  (  __main_block_func_0  ,  &__main_block_desc_0_DATA ) ;


    return 0;
}

查看 __main_block_impl_0这个函数

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;
  }
    
};

下面 我们看一下block是如何发起调用的。

依旧是这段代码,打开 下方的 block()调用。

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);

看到这里 我们就明白了,此时发起调用,它是 将 block指针强转为__block_impl类型。并获取之前存入的 FuncPtr 发起函数调用,并将 block指针作为参数传入。

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;
}

再次看下__main_block_impl_0结构体变化

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;
  }
};

再次 看 __block_implFuncPtr 赋值 也就是外界传进来的 __main_block_func_0 函数实现

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


        printf("LG_Cooci%d",a);
    }

为了彻底弄清楚 我们 写一个我们平常的oc 对象,在block块内部引用

请问下面输出什么?

      LGPerson * person = [[LGPerson alloc]init];
      person.tag = @"等风来不如追风去,总有那么一个人在风景正好的季节来到你的身边";
     
       void(^block)(void) = ^{
 
            NSLog(@"%@",person.tag);
            
        };
 
        person.tag = @"45°仰望天空,该死我那无处安放的魅力";
 
        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 构造函数 结构体赋值
        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);

        ///发起调用
         ((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        

再次 看此时的 __main_block_impl_0结构体

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;
  }
};

在看一下方法

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")));

          }

值copy 此时 内存空间 两个一样的 内容。指针 不一样。也就是深拷贝。
指针 copy 此时 copy了一个指针,两个指针指向同一片内存区域。也就是浅拷贝。

我们对于 值拷贝的基础数据类型的捕获 该如何操作呢?

__block

在什么情况下我们需要用__block的修饰?

__block又做了哪些事情?带着疑问向下分析

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

继续 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);




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修饰 我捕获一个字符串,它也是指针那为啥 当我在对block发起调用前重新修改 字符串的值,它怎么数据不同步呢?

    NSString * str = [NSString stringWithFormat:@"等风来不如追风去啊"];
           
           void (^block)(void) = ^{
             
               NSLog(@"%@,%p",str,str);
           };
         
           str = @"总有一个人,在风景正好的季节等着你";
           NSLog(@"%@,%p",str,str);

        
           block();
        
    ///字符串指针
         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);

        ///发起调用
         ((__block_impl *)block)->FuncPtr)((__block_impl *)block);

看func函数

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);
           }
    }

继续带着这个疑问 我们打印一下上下str的指针指向地址。


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

__block修饰 运行

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

为了验证我们的想法 再次查看用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结构体对象
         __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));

        ///函数调用
        (__block_impl *)block)->FuncPtr)((__block_impl *)block);
        

在看一下 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));
           }

总结

block真正的类型

打开汇编,并在下面区域打上断点


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

运行


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

我们看到到了callq 了 几个很重要的函数 一个

分别符号断点下这个 看他来自哪个"星球"
断点 objc_retainBlock

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

objc4源码全局搜索 objc_retainBlock

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

全局搜索 _Block_copy发现Objc并未发现什么

那接着下符号断点吧它肯定不来自这个库了。


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

官网找到开源库全局搜索 _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`类型
    struct Block_layout *aBlock;

    // 没有内容,直接返回空
    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    // 将内容转变为`Block_layout`结构体格式
    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.
        // 进入的是栈区block,拷贝一份
        // 开辟一个大小空间的result对象
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        // 开辟失败,就返回
        if (!result) return NULL;
        // 内存拷贝:将aBlock内容拷贝到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
        // 与操作,结果为前16位都为0 引用计数为0
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        // 设置为需要释放,引用计数为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.
        // 设置isa为堆区Block
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

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

查看 Flags:标识码

// 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
};

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

总结

查看 _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
}

查看descriptor访问操作

#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;
}

看到这里我们应该更能体会到 descriptor 属性及上面的附加可选什么意思 下面画个图


通过指针平移获取desc

以上为我们开了上帝视角 下面我们实际操作 亲眼所看到 从栈block 拷贝到堆的过程

上面我们已经通过阅读源码知道了 当底层调用完Block_copy 其真实的block类型就会确定所以我们在调用之前打断点读取

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

按住 ctrl + 鼠标点击 向下箭头 ,跳进 objc_retainBlock 方法继续打印


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

跳进了 objc_retainBlock


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

打入objc_retainBlock的全局断点 并继续读取


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

按住 ctrl + 鼠标点击 向下箭头 继续向下走


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

继续跟进跳转


截屏2020-12-11 下午2.14.45.png 截屏2020-12-11 下午2.16.19.png

我们分析了block是如何确定最终类型的,那还是不了解block是如何捕获外界变量的,为什么__block修饰后 数据会同步呢? 下面我继续分析 底层

先看图


__block clang.jpg

源码搜索 _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 被一个 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;
    }
}

查看 枚举 值

 
// 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
    //普通对象,即没有其他的引用类型
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    //block类型作为变量
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    //经过__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
    //返回的调用对象 - 处理block_byref内部对象内存会加的一个额外标记,配合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) {
    
    //强转为Block_byref结构体类型,保存一份
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack 申请内存
        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内部持有的Block_byref 和 外界的Block_byref 所持有的对象是同一个,这也是为什么__block修饰的变量具有修改能力
        //copy 和 scr 的地址指针达到了完美的同一份拷贝,目前只有持有能力
        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是结构体,__block修饰的可能是对象,对象通过byref_keep保存,在合适的时机进行调用
            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;
            }
            //等价于 __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_byref 结构体

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; // 结构体 __block  对象
    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
};

在_Block_byref_copy中我们看到src2->byref_keep,其实就是调用外部的__Block_byref_id_object_copy_131,为什么?
这里我们 看 Block_byref_2 中两个函数 ,clang编译器中的两个函数

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep; // 结构体 __block  对象
    BlockByrefDestroyFunction byref_destroy;
};

截屏2020-12-14 上午11.24.26.png 截屏2020-12-14 上午11.22.55.png

原因


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

而131 = 128 +3,其中128表示BLOCK_BYREF_CALLER --> 代表__block变量有copy/dispose的内存管理辅助函数

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

我们这里示例的对象类型为NSString,就表示上述枚举中这个 BLOCK_FIELD_IS_OBJECT,也就是继承NSObjcet类型的 id类型的 为3,然后和copy函数拼接起来就是 __Block_byref_id_object_copy_131

所以在_Block_byref_copy 中以下标红出就相当于 __Block_byref_id_object_copy_131的调用


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

而这里的调用又会触发 _Block_object_assign

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

总结

详细总结:

Block真正的底层是Block_layout 对象,clang编译器 会根据捕获类型,来动态的改变,及生成对应的数据结构。如用__block修饰后的对象,clang编译器会将其封装为一个byref的结构体对象,此结构体对象在底层真正的类型为 Block_byref 结构体。
在运行时 会调用Block_copy 函数 通过 block_layout对象中的flags标记 判断当前block的类型及状态。如果是需要释放的 那么 只操作引用计数并返回,如果是全局block那么直接返回,如果是栈区的block, 开辟内存空间 ,设置属性为堆区的标识及一些设置。其中最具代表性的属性为 desc ,在默认情况下block的描述desc只有一个,当被__block修饰之后 ,clang编译器会在desc结构体中多出两个函数copy/dispose 底层会根据 block的flags 标识 来判断是否拥有 desc2 或者 desc3 的block的拓展信息,如判断拥有 copy/dispose 函数,那么会执行copy函数此时会调用Block_object_assign函数 此函数中同样的会判断当前捕获的是什么类型,进行不同的处理, 此时是__block修饰的变量也就byref结构体 将会掉起 _Block_byref_copy 函数,此函数正是对byref结构体 从栈中copy到堆中的操作, 类似block的copy。首先开辟内存,设置 属性为堆区的标识及一些设置,这里重要的操作为,将堆区的forwarding指针 指向 堆区的Block_byref自己.将栈区的forwarding指针指向更改为堆区的Block_byref结构体。并设置 栈区的大小。同样根据栈区的byref标识flags判断是否支持 copy/和dispose函数,如果支持,通过指针平移获取栈区堆区的 Block_byref2 拓展结构体, 从栈区的这两个函数指针赋值 堆区的 Block_byref2 中。再此判断中还判断了是否支持layout拓展,如支持 同样通过指针平移获取栈区堆区的 Block_byref3拓展结构体,从栈区的这个函数指针赋值 堆区的 Block_byref3 中.
如支持copy/dispose 函数 那么将再次发起 Block_object_assign函数调用,此时进行的是通过Block_byref结构体偏移获取被修饰的指针变量进行 指针copy 也就是引用计数+1

非太详细:

也就是 __block修饰的基本数据类型会进行 二次copy 一个是block的copy 一个是byref结构体的copy 都是从 栈中 copy到堆中。

如果修饰的是指针类型,那么会进行三次 copy,前两次和上面一样,最后一次 会对修饰的原始指针,进行 指针copy引用计数+1.

上一篇 下一篇

猜你喜欢

热点阅读