美文网首页iOS
block 之源码实现(总有你想不到的地方)

block 之源码实现(总有你想不到的地方)

作者: 充满活力的早晨 | 来源:发表于2018-09-05 18:28 被阅读102次

    以前写的分析block的源码博客,写的地方台分散了,不知道弄哪里去了。特此再写一遍吧,加深印象。

    这里我们还是先采用来路,编译一段代码,将其翻译成c++ 文件。

    工程创建

    image.png

    将main.m 文件中的main函数修改成如下

    int main(int argc, const char * argv[]) {
        void (^block)(void)=^(void){
            NSLog(@"hello world");
        };
        block();
    }
    }
    

    打开终端
    cd 到main.m 所在目录,输入下面命令

    clang -rewrite-objc main.m 
    open .
    

    我们发现发现打开的文件中生成了一个main.cpp文件,这就是我们的起始分析文件了。

    这个文件比较大,我们就找我们需要的部分
    将文件几乎拖到最低下,就是我们写的代码编译器编译的结果

    先看实现

    int main(int argc, const char * argv[]) {
        void (*block)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
    }
    

    main 函数变成了上面的样子。
    先分析

        void (*block)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    

    这块对应block的声明部分

        void (^block)(void)=^(void){
            NSLog(@"hello world");
        };
    

    从声明部分,我们需要弄明白__main_block_impl_0__main_block_func_0,** __main_block_desc_0_DATA** 。这三个是什么东西
    搜索main.cpp 文件

    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;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_3w_qq4772l51dsg0hkfxw63s8sc0000gp_T_main_f86fd3_mi_0);
        }
    
    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 结构体有两个成员变量,struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    结构体有个初始化方法(c++ 的结构体里面可以包含方法的),这个初始化方法传入了一个函数指针,和__main_block_desc_0类型的结构体指针。
    __main_block_func_0 函数传入一个__main_block_impl_0 类型的结构体指针
    __main_block_desc_0 结构体实例化一个对象是__main_block_desc_0_DATA ,该对象记录__main_block_impl_0 的大小

    从这里我们能看出block的声明其实就是生成一个结构体而已。这个结构体包含了我们需要执行的信息

    声明一个结构体如下


    声明一个block

    这个就是block的具体结构了
    我们看看这个结构体的参数都是什么意思
    我们调用的是__main_block_impl_0 的初始化方法,传入的参数是__main_block_func_0函数指针 和 __main_block_desc_0_DATA 实例化的结构体指针。__main_block_desc_0_DATA 结构体包含__main_block_impl_0结构体的大小。

    struct __block_impl->isa 表明这个block 的类型,这里是&_NSConcreteStackBlock
    struct __block_impl->Flags 。这里是0
    struct __block_impl->FuncPtr 这个就是函数实现的指针了
    struct __block_impl->reserved 。保留位。没用
    struct __main_block_desc_0->reserved 保留位置
    struct __main_block_desc_0-> 记录__main_block_impl_0 结构体的大小。

    我们声明block其实就是获取了一个结构体而已
    那么我们调用block 发生了什么事情了呢?

      ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    

    这个写法看上去怎么这么难看啊。
    不要紧,我们知道block声明的最终结果是生成了一个结构体__main_block_impl_0,这个结构体包含了我们需要的函数,要是我们想从这个结构体中调用函数怎么办呢?
    很简单

    struct __main_block_impl_0 ->struct __block_impl impl->void *FuncPtr;找到这个函数调用这个函数就是了

    (__block_impl )block)->FuncPtr block是__main_block_impl_0 类型结构体, 因为__block_impl 在结构的开头,所以地址和__main_block_impl_0 一样,强制转换就行了,我们从__block_impl 结构体中找到函数指针FuncPtr。 相当于void * p = (__block_impl )block)->FuncPtr ;
    找到函数指针了,那么我们就调用函数指针。因此这个函数的参数需要一个参数是__main_block_impl_0 ,所以把自己穿进去就是了。因此就是p(block)

    简化处理就是这个样子

    block-impl->FuncPtr(block)
    

    block到此最简单的就实现完毕了。

    最简单的分析完毕了。

    block的捕获功能

    int main(int argc, const char * argv[]) {
    
        int param= 3;
        void (^block)(void)=^(void){
            NSLog(@"hello world %d",param);
        };
        
        block();
        
    }
    

    将main 函数改成上面的样子,重新编译

     clang -rewrite-objc main.m
    

    打开main.cpp 文件,看看有啥变化

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int param;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _param, int flags=0) : param(_param) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int param = __cself->param; // bound by copy
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_3w_qq4772l51dsg0hkfxw63s8sc0000gp_T_main_928200_mi_0,param);
        }
    
    

    我们发现__main_block_impl_0 结构体有变化,多了个 int param;变量,并且初始化参数多了一个,将param赋值
    __main_block_func_0 函数中获取参数是从__main_block_impl_0 中获取的。

    void (*block)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, param))
    

    block的声明的时候是将 param传入进去的。这就是block的捕获功能,其实就是编译阶段将值提前保存起来了而已。并且** __main_block_impl_0** 大小也发生了变化,如图


    block的捕获功能

    __block 修饰的的参数的block

    int main(int argc, const char * argv[]) {
       __block int param= 3;
        void (^block)(void)=^(void){
            NSLog(@"hello world %d",param);
        };
        block();
    }
    

    将main函数修改成上面的样子

     clang -rewrite-objc main.m
    

    打开main.cpp 文件,看看有啥变化
    变化不小

    struct __Block_byref_param_0 {
      void *__isa;
    __Block_byref_param_0 *__forwarding;
     int __flags;
     int __size;
     int param;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_param_0 *param; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_param_0 *_param, int flags=0) : param(_param->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_param_0 *param = __cself->param; // bound by ref
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_3w_qq4772l51dsg0hkfxw63s8sc0000gp_T_main_abdc39_mi_0,(param->__forwarding->param));
        }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->param, (void*)src->param, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->param, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    int main(int argc, const char * argv[]) {
    
       __attribute__((__blocks__(byref))) __Block_byref_param_0 param = {(void*)0,(__Block_byref_param_0 *)&param, 0, sizeof(__Block_byref_param_0), 3};
        void (*block)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_param_0 *)&param, 570425344));
    
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
    }
    

    我把所以的相关代码都贴出来了
    一点点分析,我们知道block的声明就是通过__main_block_impl_0 的初始化函数生成一个__main_block_impl_0类型的结构体。
    先看声明

        void (*block)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_param_0 *)&param, 570425344));
    

    看看初始化__main_block_impl_0 传入的参数

    1 __main_block_func_0 ,函数结构体指针
    2 __main_block_desc_0_DATA
    3 __Block_byref_param_0 结构体,
    4 flag ,这里注意,flag 这里已经不是0了。值是570425344

    这里多了个__Block_byref_param_0 结构体,我们先看这个结构体

    struct __Block_byref_param_0 {
      void *__isa;
    __Block_byref_param_0 *__forwarding;
     int __flags;
     int __size;
     int param;
    };
    

    看这个结构体,如下图


    __Block生成的结构体

    看看给这个结构体如何传参的

      __attribute__((__blocks__(byref))) __Block_byref_param_0 param = {(void*)0,(__Block_byref_param_0 *)&param, 0, sizeof(__Block_byref_param_0), 3};
    

    这里

    1 void *__isa = (void *)0
    2 __Block_byref_param_0 *__forwarding;指向自己
    3 int __flags = 0;
    4 int __size; 结构体大小
    5 int param; 就是我们参数值

    再看看 __main_block_impl_0 结构体的变化,这里多了个__Block_byref_param_0 *param; // by ref 参数。
    这个参数是如何赋值的呢?

     __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_param_0 *_param, int flags=0) : param(_param->__forwarding)
    

    C++ 写法,param(_param->__forwarding) ,给__main_block_impl_0 的 param赋值是取的是传入进来的__Block_byref_param_0 的__forwarding 。为什么是这样呢?而不取自己呢?
    其实是这样的,当我们将block做copy 的时候,__Block_byref_param_0 也被拷贝到堆上,执行下图逻辑

    __block 声明变量copy到堆上的过程

    如何验证呢?
    首先要将main文件改成mrc,见图


    改成mrc

    然后修改main函数如下

    #import <Foundation/Foundation.h>
    
    
    struct Block_byref_param {
        void *__isa;
        struct Block_byref_param *__forwarding;
        int __flags;
        int __size;
        int param;
    };
    
    struct __Test_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    
    static struct block_desc_0 {
        size_t reserved;
        size_t Block_size;
        void (*copy)(void);
        void (*dispose)(void*);
    };
    
    struct Test_impl_0 {
        struct __Test_impl impl;
        struct block_desc_0* Desc;
        struct  Block_byref_param *param; // by ref
    
    };
    
    int main(int argc, const char * argv[]) {
    
       __block int  param= 3;
        struct Block_byref_param * paramStruct = (struct Test *) &param;
        NSLog(@"copy前 value 指针 = %p (这里取的是Block_byref_param-->forward->param的地址,因为只要我们调用param,编译器都会给我们自动转换成这个地方,我们只能监控value指针的变化)",paramStruct);
        void (^block)(void)=^(void){
            NSLog(@"hello world %d",param);
        };
    
        struct Test_impl_0 * notCopy =(__bridge struct Test_impl_0 *)block;
        NSLog(@"notCopy %p, forward %p value =%p",notCopy->param,notCopy->param->__forwarding,&notCopy->param->__forwarding->param);
        param = 4;
         void (^block1)(void) = [block copy];
        NSLog(@"=====copy block 之后=======");
       struct Test_impl_0 * notCopy1 =(__bridge struct Test_impl_0 *)block1;
        paramStruct = (struct Test *) &param;
        NSLog(@"copy后 __block value  指针 = %p",paramStruct);
        NSLog(@"notCopy %p, forward %p value =%p",notCopy->param,notCopy->param->__forwarding,&notCopy->param->__forwarding->param);
        NSLog(@"Copy %p, forward %p value =%p",notCopy1->param,notCopy1->param->__forwarding,&notCopy1->param->__forwarding->param);
    
        block();
        
    }
    

    测试结果

    2018-09-06 11:25:41.701063+0800 Block[90358:9195963] copy前 value 指针 = 0x7ffeefbff5b8 (这里取的是Block_byref_param-->forward->param的地址,因为只要我们调用param,编译器都会给我们自动转换成这个地方,我们只能监控value指针的变化)
    2018-09-06 11:25:41.701371+0800 Block[90358:9195963] notCopy 0x7ffeefbff5a0, forward 0x7ffeefbff5a0 value =0x7ffeefbff5b8
    2018-09-06 11:25:41.701650+0800 Block[90358:9195963] =====copy block 之后=======
    2018-09-06 11:25:41.701681+0800 Block[90358:9195963] copy后 __block value  指针 = 0x10064a778
    2018-09-06 11:25:41.701696+0800 Block[90358:9195963] notCopy 0x7ffeefbff5a0, forward 0x10064a760 value =0x10064a778
    2018-09-06 11:25:41.702027+0800 Block[90358:9195963] Copy 0x10064a760, forward 0x10064a760 value =0x10064a778
    2018-09-06 11:25:41.702075+0800 Block[90358:9195963] hello world 4
    

    1 __block 修饰的变量 在copy block 之后取值的地址发生了变化,copy之前在栈上取值,copy之后就在堆上取值。
    2 没copy block 之前,block捕获的变量都是在栈上的,copy之后变量也没发生变化。看打印只是更改了变量了__forward指针,这是因为栈上的block变量指向的是栈上的变量,而栈上的变量发生了变化
    3 copy block 之后,block完全copy 到堆上了,变量发生了变化,重新指向了新的变量地址,并且修改栈上的变量的__forward指针。

    我们发现struct __main_block_desc_0 ,增加了两个东西

    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    

    增加了函数指针,从字面意识是copy 和释放。真正指向的地址是__main_block_copy_0__main_block_dispose_0

    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->param, (void*)src->param, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->param, 8/*BLOCK_FIELD_IS_BYREF*/);}
    

    这里很简单了,就是对我们__block 修饰的参数进行 保存

    __weak 修饰符

    int main(int argc, const char * argv[]) {
       __weak  int  param= 3 ;
        void (^block)(void)=^(void){
            NSLog(@"hello world %d",param);
        } ;
      
        block();
    }
    

    重写找不同的地方

    __attribute__((objc_ownership(weak))) int param= 3 ;
    

    只是将该变量加入到weak表中而已,和正常使用变量一样的,不可以在block中修改改变量的值。(不要误会,该变量一般是对象,对象不可以修改,但是属性是可以改的)

    我们知道__weak 一般都是用来修饰对象的,这里修饰了个变量,因此和没有修饰是一样的。
    我们把变量改成对象的时候编译器会报错

    int main(int argc, const char * argv[]) {
    
      id   __weak   param= [[NSObject alloc]init];
        void (^block)(void)=^(void){
            NSLog(@"hello world %@",param);
        };
    
        block();
        
    }
    
    编译器报错

    我们用下面的命令就可以了

    clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations main.m
    

    变化

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __weak id param;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __weak id _param, int flags=0) : param(_param) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    结构体中的捕获的对象是用__weak 修饰的,并且该对象放在了__weak表中

    __strong 修饰的参数block

    对对象强引用,这里就不做介绍了。

    block 捕获全局变量和静态变量

    int globeParam = 4;
    int main(int argc, const char * argv[]) {
    
        int param = 3;
        static int staticParam = 6;
        void (^block)(int mm)=^(int mm){
            NSLog(@"hello world %d %d %d %d",mm ,staticParam,globeParam,param);
        };
      block(4);
    }
    

    重编译之后看看结果

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int *staticParam;
      int param;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_staticParam, int _param, int flags=0) : staticParam(_staticParam), param(_param) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    

    我们发现局部静态变量是可以被捕获的,而全局变量是没有捕获的,不过局部静态变量保存的是静态变量的指针。

    static void __main_block_func_0(struct __main_block_impl_0 *__cself, int mm) {
      int *staticParam = __cself->staticParam; // bound by copy
      int param = __cself->param; // bound by copy
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_3w_qq4772l51dsg0hkfxw63s8sc0000gp_T_main_a33e2a_mi_0,mm ,(*staticParam),globeParam,param);
        }
    

    栈block ,堆block ,Global block

    我们知道block 可以在堆上栈上和全局
    栈上和全局block 编译阶段我们就能看出来

    int globeParam = 4;
    
    void (^blocks)(int mm) =^(int mm){
        
    };
    
    int main(int argc, const char * argv[]) {
    
        int param = 3;
        static int staticParam = 6;
        void (^block)(int mm)=^(int mm){
            NSLog(@"hello world %d %d %d %d",mm ,staticParam,globeParam,param);
        };
      block(4);
      
    }
    

    我们通过 isa指针就能区分出来

    struct __blocks_block_impl_0 {
      struct __block_impl impl;
      struct __blocks_block_desc_0* Desc;
      __blocks_block_impl_0(void *fp, struct __blocks_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteGlobalBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int *staticParam;
      int param;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_staticParam, int _param, int flags=0) : staticParam(_staticParam), param(_param) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    

    堆上的block 必须是要经过copy 之后才能到堆上的。

    void (^blocks)(int mm) =^(int mm){
        
    };
    int globeParam =100;
    int main(int argc, const char * argv[]) {
    
        int param = 3;
        static int staticParam = 6;
        void (^block)(int mm)=^(int mm){
            NSLog(@"hello world %d %d %d %d",mm ,staticParam,globeParam,param);
        };
       
         void (^__weak block1)(int mm) = block;
        NSLog(@"%@",block1);
      block(4);
      
    }
    

    打印结果

    2018-09-06 14:33:08.775812+0800 Block[93154:9276897] <__NSStackBlock__: 0x7ffeefbff588>
    (lldb) 
    

    说明经过copy 的block是放堆上的

    看源码

    void *_Block_copy(const void *arg) {
        return _Block_copy_internal(arg, true);
    }
    
    static void *_Block_copy_internal(const void *arg, const bool wantsOne) {
        struct Block_layout *aBlock;
    
        if (!arg) return NULL;
        
        
        // The following would be better done as a switch statement
        aBlock = (struct Block_layout *)arg;
        if (aBlock->flags & BLOCK_NEEDS_FREE) {
            // latches on high
            latching_incr_int(&aBlock->flags);
            return aBlock;
        }
        else if (aBlock->flags & BLOCK_IS_GC) {
            // GC refcounting is expensive so do most refcounting here.
            if (wantsOne && ((latching_incr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK) == 2)) {
                // Tell collector to hang on this - it will bump the GC refcount version
                _Block_setHasRefcount(aBlock, true);
            }
            return aBlock;
        }
        else if (aBlock->flags & BLOCK_IS_GLOBAL) {
            return aBlock;
        }
    
        // Its a stack block.  Make a copy.
        if (!isGC) {
            struct Block_layout *result = malloc(aBlock->descriptor->size);
            if (!result) return NULL;
            memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
            // reset refcount
            result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
            result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
            result->isa = _NSConcreteMallocBlock;
            _Block_call_copy_helper(result, aBlock);
            return result;
        }
        else {
            // Under GC want allocation with refcount 1 so we ask for "true" if wantsOne
            // This allows the copy helper routines to make non-refcounted block copies under GC
            int32_t flags = aBlock->flags;
            bool hasCTOR = (flags & BLOCK_HAS_CTOR) != 0;
            struct Block_layout *result = _Block_allocator(aBlock->descriptor->size, wantsOne, hasCTOR || _Block_has_layout(aBlock));
            if (!result) return NULL;
            memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
            // reset refcount
            // if we copy a malloc block to a GC block then we need to clear NEEDS_FREE.
            flags &= ~(BLOCK_NEEDS_FREE|BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);   // XXX not needed
            if (wantsOne)
                flags |= BLOCK_IS_GC | 2;
            else
                flags |= BLOCK_IS_GC;
            result->flags = flags;
            _Block_call_copy_helper(result, aBlock);
            if (hasCTOR) {
                result->isa = _NSConcreteFinalizingBlock;
            }
            else {
                result->isa = _NSConcreteAutoBlock;
            }
            return result;
        }
    }
    
    

    xcode5 以后,苹果使用LLVM编译器,不是用gcc

    static bool isGC = false;
    

    源码写的很明确,不是gcc ,block 的isa指针指向** _NSConcreteMallocBlock**。这里也有很明显的malloc操作

    这里不想做过多介绍
    _NSConcreteFinalizingBlock_NSConcreteAutoBlock 是用在gcc时代不做介绍了

    block 的flag 参数

    // 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 的结构标志

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

    这个是参数的标志位的flags

    这里有个** BLOCK_HAS_COPY_DISPOSE** 枚举,这个就是标记是否有参数需要进行copy 或者dispose 的。只要block 对变量进行捕获了,那么flag 值就是BLOCK_HAS_COPY_DISPOSE | BLOCK_USE_STRET =570425344; 看图更明确了


    带参数的blockflag标志位

    block

    上面我们分析过了block的结构
    不过我们从源码中也找到了苹果给定的block结构

    struct Block_layout {
        void *isa;
        volatile int32_t flags; // contains ref count
        int32_t reserved; 
        void (*invoke)(void *, ...);
        struct Block_descriptor_1 *descriptor;
        // imported variables
    };
    

    我们现在写代码一般都是在ARC环境下写block,因此我们能看见的block只有两种,堆上或者Global
    堆上的block,我们看看是如何生成的

      if (!isGC) {
            struct Block_layout *result = malloc(aBlock->descriptor->size);
            if (!result) return NULL;
            memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
            // reset refcount
            result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
            result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
            result->isa = _NSConcreteMallocBlock;
            _Block_call_copy_helper(result, aBlock);
            return result;
        }
    

    在llvm 环境下,block的flags 值是(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);
    是支持签名的。

    写段小代码

    
    struct Block_byref_param {
        void *__isa;
        struct Block_byref_param *__forwarding;
        int __flags;
        int __size;
        int param;
    };
    
    struct __Test_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    
    static struct block_desc_0 {
        size_t reserved;
        size_t Block_size;
        void (*copy)(void);
        void (*dispose)(void*);
    };
    
    struct Test_impl_0 {
        struct __Test_impl impl;
        struct block_desc_0* Desc;
        struct  Block_byref_param *param; // by ref
    
    };
    
    void mm (struct Test_impl_0 * ddd,int m);
    
    void (^blocks)(int mm) =^(int mm){
        
    };
    int globeParam =100;
    int main(int argc, const char * argv[]) {
    
     __block   int param = 3;
        static int staticParam = 6;
        void (^block)(int mm)=^(int mm){
            NSLog(@"hello world %d %d %d %d",mm ,staticParam,globeParam,param);
        };
        struct Test_impl_0 * blockStr = (__bridge struct Test_impl_0 *)block;
        void (*p)(struct Test_impl_0 * ddd,int m)  =  blockStr->impl.FuncPtr;
        p(blockStr,1);
    }
    
    image.png

    我们发现,我们调用到了block 里面的实现。

    其实看这段代码,总结起来就是block是个结构体,但是这个结构体里面有个指针是指向一个函数的。因此好多人也把block叫做匿名函数。

    如果我们能对这个函数的签名,那岂不是我们就可以以函数的形式调用这个block了。

    好多库都对block进行了签名解析,列举两个吧:aspects 和promise。

    // Block internals.
    typedef NS_OPTIONS(int, AspectBlockFlags) {
        AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
        AspectBlockFlagsHasSignature          = (1 << 30)
    };
    typedef struct _AspectBlock {
        __unused Class isa;
        AspectBlockFlags flags;
        __unused int reserved;
        void (__unused *invoke)(struct _AspectBlock *block, ...);
        struct {
            unsigned long int reserved;
            unsigned long int size;
            // requires AspectBlockFlagsHasCopyDisposeHelpers
            void (*copy)(void *dst, const void *src);
            void (*dispose)(const void *);
            // requires AspectBlockFlagsHasSignature
            const char *signature;
            const char *layout;
        } *descriptor;
        // imported variables
    } *AspectBlockRef;
    
    static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
        AspectBlockRef layout = (__bridge void *)block;
        if (!(layout->flags & AspectBlockFlagsHasSignature)) {
            NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
            AspectError(AspectErrorMissingBlockSignature, description);
            return nil;
        }
        void *desc = layout->descriptor;
        desc += 2 * sizeof(unsigned long int);
        if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
            desc += 2 * sizeof(void *);
        }
        if (!desc) {
            NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
            AspectError(AspectErrorMissingBlockSignature, description);
            return nil;
        }
        const char *signature = (*(const char **)desc);
        return [NSMethodSignature signatureWithObjCTypes:signature];
    }
    

    有人会问,block写出来就明确了,直接调用就行了,我还解析签名干嘛?
    这里我想说下,其实把,我们可以把block当做参数在函数中进行传递,要是你说我给定固定参数的block,没话说,根本不用解析。要是我们传入的block是id类型,这个block 可能是变参数的,这就有用了,我们首先可以在运行时动态的拿到block的函数签名,就知道有几个参数,之后我们构造响应的block结构的变量指向该block,在调用这个block就行了。

    这里是采用Aspects的技术写的简单代码片段

    int globeParam =100;
    
    // Block internals.
    typedef NS_OPTIONS(int, AspectBlockFlags) {
        AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
        AspectBlockFlagsHasSignature          = (1 << 30)
    };
    typedef struct _AspectBlock {
        __unused Class isa;
        AspectBlockFlags flags;
        __unused int reserved;
        void (__unused *invoke)(struct _AspectBlock *block, ...);
        struct {
            unsigned long int reserved;
            unsigned long int size;
            // requires AspectBlockFlagsHasCopyDisposeHelpers
            void (*copy)(void *dst, const void *src);
            void (*dispose)(const void *);
            // requires AspectBlockFlagsHasSignature
            const char *signature;
            const char *layout;
        } *descriptor;
        // imported variables
    } *AspectBlockRef;
    
    static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
        AspectBlockRef layout = (__bridge void *)block;
        if (!(layout->flags & AspectBlockFlagsHasSignature)) {
            NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
            return nil;
        }
        void *desc = layout->descriptor;
        desc += 2 * sizeof(unsigned long int);
        if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
            desc += 2 * sizeof(void *);
        }
        if (!desc) {
            NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
            return nil;
        }
        const char *signature = (*(const char **)desc);
        return [NSMethodSignature signatureWithObjCTypes:signature];
    }
    
    int main(int argc, const char * argv[]) {
    
     __block   int param = 3;
        static int staticParam = 6;
        void (^block)(void)=^{
            NSLog(@"hello world  %d %d %d" ,staticParam,globeParam,param);
        };
        NSMethodSignature * sign =aspect_blockMethodSignature(block, nil);
    
        NSInvocation * inv = [NSInvocation invocationWithMethodSignature:sign];
        [inv setTarget:block];
        [inv invoke];
    
    }
    
    

    结果


    image.png

    这里NSInvocation 的具体用法就不做介绍了

    block 定义地址
    block 源码

    相关文章

      网友评论

        本文标题:block 之源码实现(总有你想不到的地方)

        本文链接:https://www.haomeiwen.com/subject/slxawftx.html