Blocks
含义:带有自动变量值的匿名函数
Blocks 模式
Block 语法
^ 返回值类型 参数列表 表达式
与C语言函数的定义相比,不同点在于:没有函数名,带有^
(插入记号)
Block 类型变量
参数类型 (^ 参数名称) (block参数列表)
自动变量的捕获
int main() {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{printf (fmt, val); };
val = 2;
fmt = "These values were changed, val = %d\n";
blk();
return 0;
}
// 执行结果
// val = 10
复制代码
从上面的代码可以看出,Block表达式使用的变量值,是在block声明时变量的瞬间值。也就是说block会捕获自动变量的瞬时值。在之后哪怕更改了变量的值,也不会对block造成影响。
__block说明符
block所捕获的值不能再block中进行修改或重新赋值。如果想要修改捕获的值的话,需要在block外对该变量使用__block
进行修饰: __block int a = 0;
但是,对于oc对象的话,如果调用的是变更对象的方法的话,则没有问题。比方说给捕获到的数组添加元素。
Blocks 的实现
实质
int main() {
void (^blk)(void) = ^{ printf("Block\n"); };
blk();
return 0;
}
复制代码
使用-rewrite-obj
将上面的代码,转换为C++代码:
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;
__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) {
printf("Block\n");}
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, char * argv[]) {
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
复制代码
通过和源码的比对,可以看出对应的就是void (*blk) (void) = ((void(*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
这一句代码。这句代码的作用就是将栈上生成的__main_block_impl_0
结构体实例的指针赋值给blk。
__main_block_impl_0()
这个结构体的构造函数接受两个参数,第一个是c语言的函数指针,第二个是__main_block_desc_0
的结构体实例指针。
换句话说,就是将__main_block_impl_0
中的__block_impl
初始化为:
isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
复制代码
而Block的调用的话,就是简单的通过函数指针来调用函数:(*blk->impl.FuncPtr)(blk);
捕获变量
int main(int argc, char * argv[]) {
int i = 1;
void (^blk)(void) = ^{
printf("%d", i);
};
blk();
return 0;
}
复制代码
同样将上面的代码改写为C++
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int i;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int i = __cself->i; // bound by copy
printf("%d", i);
}
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, char * argv[]) {
int i = 1;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
复制代码
可以看到__main_block_impl_0
的结构体中,block外的变量被作为成员变量添加到了结构体当中。然后其构造函数变成了__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i)
这样:
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
i = 1;
复制代码
因此可以看到,block的自动捕获变量的功能,实际上是将该变量值保存到block的结构体实例中(block自身)。
__block
在block中,静态变量,静态全局变量和全局变量这三种类型的变量是允许进行改写的,除此之外,其他类型的变量,如果想在block中进行修改,就只能使用__block
变量。
为int i = 0;
加上__block
这个说明符,然后改写成C++:
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_i_0 *i; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref
printf("%d", (i->__forwarding->i));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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, char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
复制代码
从代码我们看到,变量i变成了__Block_byref_i_0
这个结构体实例(__block
修饰的变量会被转化为__Block_byref_ParameterName_0
这样的结构体),该结构体的声明如下:
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i; // 原变量
}
复制代码
如果在block中为__block
变量赋值的话,其转换代码为:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_i_0 *i = __cself->i; // bound by ref
(i->__forwarding->i) = 2;
}
复制代码
__Block_byref_val_0
结构体实例的成员变量__forwarding
是个指向实例自身的指针:
并且__Block_byref_i_0
这个结构体并不在__main_block_impl_0
这个结构体的实例中,这是因为为了在多个block中使用__block
变量
Block 存储域
Block实际上会转换为__main_block_impl_0
这样的结构体类型的自动变量,__block
变量会转换为__Block_byref_para_0
这样的结构体类型自动变量。(结构体类型自动变量指的是栈上生成该结构体的实例)
名称 | 实质 |
---|---|
Block | 栈上Block的结构体实例 |
__block变量 | 栈上__block变量的结构体实例 |
Block作为OC对象来看时,其类是_NSConcreteStackBlock
,除了这个类之外,还有:
类 | 设置对象的存储域 |
---|---|
_NSConcreteStackBlock | 栈 |
_NSConcreteGlobalBlock | 程序的数据区域 |
_NSConcreteMallocBlock | 堆 |
可以看到block的类型有三种,可是怎么知道block的类型呢?
当block为全局变量或者是未使用捕获自动变量时,为__NSConcreteGlobalBlock
,其他情况为__NSConcreteStackBlock
。 使用__block
变量时,实际上是将block和变量从栈上复制到堆上,此时,block的类型为__NSConcreteMallocBlock
Block的类 | 副本源的配置存储域 | 复制效果 |
---|---|---|
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteGlobalBlock | 程序的数据区域 | 什么也不做 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |
而对于__block
变量的话:
__block变量的配置存储域 | Block被复制到堆时的影响 |
---|---|
栈 | 从栈复制到堆并被Block持有 |
堆 | 被Block持有 |
在Block被复制到堆上的时候,原有的__forwarding
的值会指向堆上的Block的__block
变量的结构体实例的地址
捕获OC对象
通过__main_block_copy_0
和__main_block_dispose_0
来改变对象的引用计数数值。 在以下几种情况下,Block会被复制到堆上:
- 调用block的copy方法
- Block作为函数返回值返回时
- 将Block赋值给带有
__strong
的id类型的类或者Block类型的成员变量 - 使用方法名称含有
usingBlock
的Cocoa框架方法或者是GCD的block时
循环引用
解决block循环引用的方法有两种,一种是__weak
,另一种是__block
.不过是引用__block
的话,必须执行block,且在block中要将变量赋值为nil, 使用__block
解决循环引用的优点在于可以控制对象的持有时间。