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

以前写的分析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 源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容