1、Block基本信息
(1)什么是Block?
Blocks是C语言的扩充功能,即:带有自动变量(局部变量)的匿名函数。
Block本质是Objective-C对象
(2)Block语法
^<返回值类型><参数列表><表达式>;
^int(int count) {return count + 1;};
Blocks中void可以省略简写
^void(void){ print('xxxx'); };
//等价于
^{print('xxxx');};
(3)Block类型变量**
语法: <返回值类型>(^<命名>)(<参数>) 示例:int (^Block)(int) typedef int (^Block)(int);
(4)Block对局部变量的捕获**
int a = 10;
void(^blk)(void) = ^{
printf("a = %d \n",a);
};
a = 20;
blk();
输出结果
a = 10
可以看出a的值并没有因为a=20改变,在执行blk()时而改变,这就是Block对局部变量的捕获
(5)__block修饰符
block可以解决局部变量在block内被赋值,如果不使用block则直接赋值会出现编译错误。
(6)Block的实质探索
将下面代码进行实质转换
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void)=^{ printf("block\n"); };
block();
}
}
使用clang转化之后
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; // 将Block指针赋值给Block结构体成员变量isa
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 参数__cself是__main_block_impl_0结构体的指针
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; // Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
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);
//将转化为(*block->impl.FuncPtr)(blk)
}
return 0;
}
从上面转化的代码中可以看出Block最终转化为C语言结构体,出现__main_block_impl_0
,__main_block_func_0
,__main_block_desc_0
,__block_impl
,__main_block_impl_0
结构体相当于objc_object结构体的OC类对象的结构体。
我们在block中调用局部变量(自动变量)时
int a = 10;
void(^block)(void)=^{ printf("block===%d\n",a);};
block();
通过clang之后,底层源码中__main_block_impl_0
中添加了成员变量a
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;
}
};
从上述代码中,基本可以解释了,局部变量在block中被捕获的原因是block的底层结构体中添加了新的成员变量来接受外部的局部变量,所以在block执行的时候,实际就是赋值时的值。
Block不能直接使用C语言数组类型的自动变量
在Block中使用C语言字符数组时,会发生编译报错。
const char text[] = "hello";
void(^block)(void)=^{ printf("block===%c\n",text[2]);};
block();
报错信息:
Cannot refer to declaration with an array type inside block
__block说明符
我们知道在block中给局部变量进行赋值时,直接赋值则会引发编译错误。如果我们加上block则就能在block中进行赋值,以下我们探究下添加block之后,底层做了些什么?
__block int a = 10;
void(^block)(void)=^{
a = a + 10;
printf("block===%d\n",a);
};
block();
我们使用clang编译之后探究一下:
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;
__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) = (a->__forwarding->a) + 10;
printf("block===%d\n",(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;
// __block int a = 10;
__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)=^{ a = a + 10; printf("block===%d\n",a);};
void(*block)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
// block();
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
从以上的代码中可以看出加上__block
修饰符之后,底层源码中出现结构体__Block_byref_a_0
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
在__main_block_impl_0
中持有__Block_byref_a_0
的指针a,而__Block_byref_a_0
的结构体实例成员变量__forwarding
持有指向该实例自身的指针。
关于Block中的全局变量,局部变量,静态变量的探索可以阅读[*Block常见问题](https://www.yuque.com/zhangyanneng/angtqk/alhb5e)
2、Block存储域
名称 | 实质 |
---|---|
Block | 栈上Block的结构体实例 |
__block | 栈上__block变量的结构体实例 |
Block的类所处的存储域
类 | 设置对象的存储域 |
---|---|
_NSConcreteStackBlock | 栈区 |
_NSConcreteGlobalBlock | 全局区(.data区) |
_NSConcreteMallocBlock | 堆区 |
Objective-C中的内存分区
在之前的案例中impl.isa = &_NSConcreteStackBlock;
都是指向栈区的。那么全局预期的的在什么场景下出现呢?看下面的代码
// 全局的blk
void(^gblk)(void) = ^{ printf("global block \n");};
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
}
通过clang这段代码之后,我们发现impl.isa = &_NSConcreteGlobalBlock;
struct __gblk_block_impl_0 {
struct __block_impl impl;
struct __gblk_block_desc_0* Desc;
__gblk_block_impl_0(void *fp, struct __gblk_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
以上说明,全局的block的存储区域是在全局区上的。
将block作为函数返回时,编译器会自动的生成复制到堆上的代码,栈上的block会自动调用copy方法复制到堆上。实际在使用中,我们也常用copy关键字作为block的属性,或者手动复制到堆上。
Block在在使用copy方法后的行为关系
Block的类 | 副本源的配置存储域 | copy复制效果 |
---|---|---|
_NSConcreteStackBlock | 栈 | 从栈复制到堆上 |
_NSConcreteGlobalBlock | 全局区 | 什么也不做 |
_NSConcreteMallocBlock | 堆 | 引用计数加1 |
Block从栈复制到堆上时对__block变量产生的影响
__block的存储域 | Block从栈上复制到堆上的影响 |
---|---|
栈 | 从栈上复制打堆上并被Block持有 |
堆 | 被Block持有 |
在Block中使用block变量时,则当该Block从栈上复制到堆上时,使用的所有block变量必定被配置在栈上,同时这些变量也将会全部从栈上复制到堆上。如果block变量已经在堆上则不会影响,只会被Block所持有,并增加引用计数。**栈上的block变量结构体实例在从栈上复制到堆上时,实际是将成员变量forwarding指针的值替换为复制到目标堆上的block变量用结构体实例的地址。**通过这个特性,无论在Block中,还是Block外,还是栈/堆中,都能正确的访问同一个block变量。
当配置到堆上的Block被释放的时候,其所持有的block变量也将被释放,引用计数减1。
捕获对象时出现的copy和dispose
在__main_block_desc_0结构体中出现的copy和dispose成员变量,如下示例:
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和dispose两个成员变量的原因是Objective-C为了准确的把握Block从栈复制到堆以及堆上的Block进行释放的时机。以及在Block结构体中使用strong修饰符和weak修饰符的变量,也可以在恰当的时机进行初始化和释放。处了copy和dispose两个成员变量,还有作为指针赋值给这两成员变量的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->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
_Block_object_assign函数调用相当于retain实例方法的函数,将对象赋值在对象类型的结构体成员变量中。
_Block_object_dispose函数调用相当于release实例方法的函数,释放赋值在对象类型的结构体中的对象。
调用copy函数和dispose函数的时机
函数 | 调用时机 |
---|---|
copy 函数 | 栈上的Block复制到堆上时 |
dispose 函数 | 堆上的Block被废弃时 |
BLOCK_FIELD_IS_BYREF = 8 对应的是__block变量
BLOCK_FIELD_IS_OBJECT = 3 对应的是对象
**
那么什么时候栈上的Block会复制到堆上呢?
- 调用Block的copy实例方法时
- Block作为函数返回值时
- 将Block赋值给__strong修饰符id类型的类或Block类型成员变量时
- 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时
Block复制过程归结于都是自动调用了_Block_copy函数