目录 什么是block block语法 block 类型变量 截获自动变量 Block的实现原理 截获自动变量的机制 __block说明符 Block存储域
Block是C语言的扩充,是一个自动包含
局部变量的
匿名函数。在C语言中所有的函数都要有名字,声明一个C函数:
int fun(int par1);
使用C函数:
int count = fun(2);
可以通过函数指针使用函数
int (*funptr)(int) = &fun;
int count = (*funptr)(2);
block在相当程度上和函数是十分相似的,但是他是一个
匿名的函数。
block语法
我们知道了block是一个匿名的函数,除此之外block具有一个显著的特点就是block总有一个
^ ,我们看一个定义block的栗子。
clang -rewrite-objc filename
我们试着将下面的oc代码翻译成C++
void (^blk)(void) = ^{};
转换后变为:
blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA)
由此我们知道blk被转换成一个指向
__main_block_impl_0结构体,并且结构体在初始化时把函数指针和DATA传入了。
同样的,最初代码的第二行:
blk();
转换后变为:
( (void (*)(__block_impl *)) ((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
简化后:
(*blk->FuncPtr)(blk);
十分明了对吧。
截获自动变量的机制
通过上面的分析我们大概明白了block是个神马东东,也自动代码最后转换成什么样子,以及他们之间是怎么调用生效的。现在只剩下如何捕获自动变量,以及自动变量怎么使用的。
同样通过一个栗子来说明:
void add(int par1,int par2){
int tmp = par1+par2;
}
int main()
{
int a = 10;
int b = 20;
void(^blk)() = ^void (){
add(a,b);
};
blk();
return 0;
}
用
clang -rewrite-objc filename翻译得到:
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;
int b;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int _b, int flags=0) : a(_a), b(_b) {
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
int b = __cself->b; // bound by copy
add(a,b);
}
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 a = 10;
int b = 20;
void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, b));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
仔细看一下就觉得不可思议的简单,就是给
__main_block_impl_0增加两个变量,并且在初始化的时候把当前值传入
__main_block_impl_0的初始化函数中。在
__main_block_func_0使用时就可以从self中自己取出来了。原来所谓自动捕获就是赋值呀,擦,要不要这么吓唬人!!如何我们不考虑其他的细节,Block也就如此而已。but,现实是,其他的细节恐怖的让人绝望。下面来分析一些。
__block说明符
我们将刚刚的栗子稍微修改一下:
void add(int par1,int par2){
int tmp = par1+par2;
}
int main()
{
__block int a = 10;
int b = 20;
void(^blk)() = ^void (){
add(a,b);
};
a = 30;
blk();
return 0;
}
实际上上面的这样需求十分常见,想要在Block中修改变量在Block外面也能使用。这是一个传值的利器,我们来看看编译器做了什么。同样使用
clang -rewrite-objc filename:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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;
int b;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _b, __Block_byref_a_0 *_a, int flags=0) : b(_b), 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
int b = __cself->b; // bound by copy
add((a->__forwarding->a),b);
}
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()
{
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
int b = 20;
void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, b, (__Block_byref_a_0 *)&a, 570425344));
(a.__forwarding->a) = 30;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
仅仅只是加了一个
__block就导致代码有很大的增加,具体来说就是原来的
int a变成了一个
__Block_byref_a_0。这样就可以把时间变量放入结构体中从而达到修改可以保留的目的。
Block存储域
回顾一下刚刚写的内容,block被转换为一个结构体局部变量,__block 修饰的临时变量也被转换为一个结构体局部变量。Block同时也是一个objective-c对象,它的类型为
_NSConcreteStackBlock。但我们还不知道
_NSConcreteStackBlock是什么,实际上通过查看源码我们知道类似的类型还有:
_NSConcreteStackBlock _NSConcreteGlobalBlock _NSConcreteMallocBlock
通过单词中的stack、global、malloc,我们可以猜测:
|
|
---|---|
|
|
|
|
|
|
我们观察下面的代码:
void print(){}
void(^blk)() = ^void (){print();};
int main()
{
blk();
return 0;
}
转换后的代码为:
struct __blk_block_impl_0 {
struct __block_impl impl;
struct __blk_block_desc_0* Desc;
__blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blk_block_func_0(struct __blk_block_impl_0 *__cself) {
print();}
static struct __blk_block_desc_0 {
size_t reserved;
size_t Block_size;
} __blk_block_desc_0_DATA = { 0, sizeof(struct __blk_block_impl_0)};
static __blk_block_impl_0 __global_blk_block_impl_0((void *)__blk_block_func_0, &__blk_block_desc_0_DATA);
void(*blk)() = ((void (*)())&__global_blk_block_impl_0);
int main()
{
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
从上面看到通过什么全局变量blk,转换后的Block对象类型是
_NSConcreteGlobalBlock ,并用static声明表示存储在数据区。不存在捕获变量的问题。
那么现在还有一个
_NSConcreteMallocBlock悬而未决,我们不着急用
clang -rewrite-objc filename 生产一个栗子。我们先考虑一个问题:局部的Block在超出其作用域后会如何
类型为
_NSConcreteGlobalBlock的block,也就是全局Block变量,在定义后的任何地方都可以使用。但案例栈上的Block,如果其所属的变量作用域结束,该Block也就被废弃了。同样由
__block修饰的变量在超出作用域的时候也会被废弃.
如果想在超出Block定义范围以外的地方使用Block就必须先把Block赋值到堆上,再把指针传过去供别人使用。回忆一下,一般是函数参数,函数返回值,赋值类的Block变量,这些地方是符合
超出Block定义范围以外的地方使用Block。
那为题来了,我们如何把一个Block赋值的推上呢?复杂吗?平时使用他没有感觉到赋值到堆上的操作呀。这是因为一切都是自动的,或者手动很简单就赋值好了。
原因是因为在大多数情况下(函数参数,函数返回值,赋值类的Block变量等),编译器会判断我们很可能在作用域以外的地方还要使用这个Block,因此自动的帮我们赋值到堆上去了,并且把指针传出。
很遗憾没能获得中间自动转换后的代码
但一个block从栈上复制到堆上后,使用__block修饰的变量也会发生变化。变量会复制到堆上并被Block持有。
现在可以讨论一下刚刚遇到的由__block修饰的变量生成的结构体。
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
回顾之前代码,发生所有访问
a的地方都是通过
__forwarding指针进行,并且这个指针指向自己。之所有这么设计是因为当 _block修饰的变量复制到堆上后,要保证栈上的变量和堆上的变量指向同一个地方。这时候
__forwarding就起作用了。
__block修饰的变量在赋值到堆上时,大概的步骤是:
malloc 创建内存区 memcopy赋值 修改原对象的__forwarding指针指向目标对象
为了验证,我们可以修改之前代码,加上
copy让Block赋值到堆上:
void add(int par1,int par2){
int tmp = par1+par2;
}
int main()
{
__block int a = 10;
int b = 20;
void(^blk)() = [^void (){
add(a,b);
} copy];
a = 30;
blk();
return 0;
}
转换后:
void add(int par1,int par2){
int tmp = par1+par2;
}
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;
int b;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _b, __Block_byref_a_0 *_a, int flags=0) : b(_b), 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
int b = __cself->b; // bound by copy
add((a->__forwarding->a),b);
}
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()
{
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
int b = 20;
void(*blk)() = (void (*)())((id (*)(id, SEL, ...))(void *)objc_msgSend)((id)((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, b, (__Block_byref_a_0 *)&a, 570425344)), sel_registerName("copy"));
(a.__forwarding->a) = 30;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
其中
__main_block_copy_0会被Block对象在调用copy时调用。真正起作用的是
_Block_object_assign((void*)&dst->a, (void*)src->a, 8);,代码很直白如下:
/*
* When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
* to do the assignment.
*/
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
//printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
_Block_assign_weak(object, destAddr);
}
else {
// do *not* retain or *copy* __block variables whatever they are
_Block_assign((void *)object, destAddr);
}
}
else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF) {
// copying a __block reference from the stack Block to the heap
// flags will indicate if it holds a __weak reference and needs a special isa
_Block_byref_assign_copy(destAddr, object, flags);
}
// (this test must be before next one)
else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
// copying a Block declared variable from the stack Block to the heap
_Block_assign(_Block_copy_internal(object, flags), destAddr);
}
// (this test must be after previous one)
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
//printf("retaining object at %p\n", object);
_Block_retain_object(object);
//printf("done retaining object at %p\n", object);
_Block_assign((void *)object, destAddr);
}
}
这里说明一下,声明时候Block会被复制到堆上:
调用Block的copy实例方法 Block作为函数的返回值时 将Block赋值给带有__strong修饰符修饰的(id类型或Block类型)成员变量时