导论
block在我们日常开发过程中经常见到,UIView动画你可以见到,视图跳转你可以看到,多线程你也可以看到,但是似乎我们有的时候只会使用,对于其内部实现并没有那么清楚,接下来就让我们一起看看block内部到底做了些什么.由于本人才疏学浅,如有不对的地方,欢迎大家及时指正
在本篇你能了解到
- 1 . block内部实现的原理
- 2 . __block到底做了什么
- 3 . block循环引用实战篇
开始进入今天的正式话题
一. block内部实现的原理
让我看下一下一段代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
void (^block)() = ^{
NSLog(@"%d",a);
};
block();
}
return 0;
}
so easy是不是,接下来我们把他转化成正在执行时的c/c++代码,将oc代码转化成c/c++代码的命令是:clang -rewrite-objc 文件名
打开终端,cd到你要转化的文件的路径下,执行clang -rewrite-objc 文件名
,此时你应该会得到一个cpp文件,这就是我们要的转换后的代码了,恩,打开它
我一点都不觉得他so easy,哈哈
不用紧张,我们不需要一行一行去看它,关键代码下面我已经贴出来了:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qv__355hmw905bf0w6tmj8t8drr0000gn_T_main_35dd2d_mi_0,a);
}
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)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//我们实际写代码的地方
int a = 10;
void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
-
1.我们开始定义的block,被赋值给了一个叫main_block_impl_0的结构体,这个结构体创建接收这样几个参数: void fp, struct __main_block_desc_0 desc, int _a, 最后一个flag默认等于0,然后为结构体内部的block_impl成员变量赋上相应的值,这个结构体就保存好我们的fp指针方便后面调用了,在创建这个结构体参数中我们需要注意的是void fp和int _a这两个参数*.
-
2 .可以看到我们传了一个叫main_block_func_0的函数给fp指针,这个函数保存了我们需要执行的代码,这个函数又接收一个叫main_block_impl_0 *的结构体,这个结构体叫cself,这个cself就是我们刚才创建的结构体了,如果不好理解的话就理解成oc中的self吧,到这里我们这个函数里面就可以拿到刚才我们创建的结构体了
-
3 .再看另外一个参数int _a: a(_a)是c++语法,意思是把_a的值赋值给a, 创建结构体的时候是直接把外面a的值传进去了,然后结构体把a的值保存下来
-
4 . 最后回到block调用上来,我们可以看到block被转化成(__block_impl *)类型的结构体,从里面的FuncPtr指针取出刚才保存的fp指针调用,然后把block传进去,到这我们已经成功调到了我们刚才保存的函数了,最后一步是从结构体中取出我们刚才保存的a值打印,这样,一个block从创建到调用的流程就完成了
到这我们可以了解到的几点是:
1 . block其实就是一个指向结构体的指针
2 . 也解释了为什么我们为什么在不加任何修饰词的时候,在block内部不能修改外部变量的原因.既然是以常量的形式传进block,赋值给结构体内部的成员变量,但是我们如果在block内部赋值,是赋给传进去的常量,常量给常量赋值,显然是不行的
二 . __block到底做了什么
以同样的方法转化成c++代码:我的源代码是这样的
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int a = 10;
void (^block)() = ^{
a = 20;
NSLog(@"%d",a);
};
block();
}
return 0;
}
这是转化后的代码:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qv__355hmw905bf0w6tmj8t8drr0000gn_T_main_2f1fbb_mi_0,(a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
同样我们一步一步来看:
-
1 .这里我们多了一个Block_byref_a_0类型的结构体,在我们加上 __block关键词之后,会创建一个Block_byref_a_0类型的结构体,我们重点关注一下创建时的第二个参数(__Block_byref_a_0 *)&a,这个表面,在创建该结构体的时候,内部会保存这个结构体的地址
-
2 . 再到我们创建block这步,第三个参数传进去的是(__Block_byref_a_0 *)&a,也就是说,刚才我们创建的结构体的地址会传进来,接着它把_a -> __forwarding给了当前结构体的a变量,也就是__Block_byref_a_0 *a; // by ref这一句,接着看_a -> __forwarding这个成员变量对应的是什么,没错,就是我们刚才创建Block_byref_a_0这个类型的结构体时保存该结构体地址的变量
-
3 . 然后我们调用_main_block_func_0(
调用block跟上面block原理描述一样,这里不多赘述
),这个函数,即调用block,我们找到_cself->a,然后再(a->_forwarding->a) = 20,回想一下刚才就知道_cself->a保存的就是上面创建的Block_byref_a_0这个结构体的地址,然后a->_forwarding也是a结构体的地址,不过一个是保存在_main_block_impl_0这个结构体中,一个保存在_Block_byref_a_0这个结构体中而已,接着,找到_Block_byref_a_0这个结构体中的a进行赋值,最后打印的也是这个值,流程和结果完全没有问题.所以__block可以修改block内部的值. -
以上部分就是关于__block在block中的具体分析
网友评论