以前写的分析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 *)¶m, 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 *)¶m, 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 *)¶m, 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 *)¶m, 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 也被拷贝到堆上,执行下图逻辑
如何验证呢?
首先要将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 *) ¶m;
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,¬Copy->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 *) ¶m;
NSLog(@"copy后 __block value 指针 = %p",paramStruct);
NSLog(@"notCopy %p, forward %p value =%p",notCopy->param,notCopy->param->__forwarding,¬Copy->param->__forwarding->param);
NSLog(@"Copy %p, forward %p value =%p",notCopy1->param,notCopy1->param->__forwarding,¬Copy1->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 的具体用法就不做介绍了
网友评论