Block
1.Block的定义和语法
2.Block的本质和分类
3.__block的实现原理
Block的定义和语法
Block是具有自动变量(局部变量)的匿名函数
自动变量
因为局部变量的作用域不是全局的,有的时候就需要对变量的值进行保存,而Block可以捕获变量,不需要使用全局变量或者静态变量,更不需要声明一个类去保存变量的值.
匿名函数
没有名字的函数,由{}包裹起来,限制了其作用域
Block和函数指针的对比
//定义一个函数
int square(int value){
return value * value;
}
//函数指针
int (*funcptr)(int) = □
int result = (*funcptr)(10);
//Block ()可以省略
^(){
NSLog(@"匿名函数");
}();
使用函数指针,当对函数指针进行赋值的时候还是需要知道函数的名字,才能知道函数的地址.
语法
^ 返回值类型 (参数列表){函数执行体||表达式};
^ 代表这是一个Block标记,便于查找
^int (int num){
return num * num;
};
//最简单的Block,没有返回值,没有参数列表可以省略
^{
printf("Block\n");
};
Block类型变量
语法:返回值类型 (^变量名) 参数列表
等同于函数指针的声明,将*换成^
//returnType (^BlockName)(type name....)
int (^blocks)(int value) = ^(int a){
return a * a;
};
block可以用作变量,函数参数,函数返回参数,结合typedef的使用更加方便
//定义int (^) int类型的Block 名字为Add
typedef int(^Add)(int num);
//作为函数返回值
Add Test(int num){
return ^int (int a){
//这里对num变量进行了捕获
return num + a;
};
}
//打印 15
NSLog(@"%d",Test(5)(10));
Block不允许修改局部变量的值
为什么?因为静态变量存在于应用程序的整个生命周期,而非静态局部变量,仅仅是存在于一个局部的上下文中。如果block执行过程中其所指向的非静态局部变量还没有被栈回收的话,这样执行没有任何问题,但是绝大多数情况下,block都是延后执行的,修改已经被回收的值很可能抛出指针异常。
其实block对局部变量进行捕获,会在block体内({}内)新定义一个变量,并进行赋值,所以在外部进行值的修改,对内部无任何影响,因为是两个不同的变量
同样当我们在Block内部修改外部变量的值,编译器会报出错,提示使用__block修饰符
__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。
int num = 2;
NSLog(@"%d,%p",num,&num);
void (^block)() = ^{
NSLog(@"%d,%p",num,&num);
};
num = 3;
block();
//打印结果:
//2,0x7ffeefbff62c
//2,0x102803880
//在block捕获之后对变量进行了改变,但是block打印的内容还是之前的值
使用__block修饰符
__block int num = 2;
NSLog(@"%d,%p",num,&num);
void (^block)() = ^{
NSLog(@"%d,%p",num,&num);
};
num = 3;
block();
//打印结果:
//2,0x7ffeefbff628
//3,0x10058ce38
//在block捕获之后对变量进行了改变,block打印的内容也发生了改变
表面上来看,默认block捕获变量是进行的值传递,使用了__block之后,传递的是地址,但是这种说法是不正确的,我们可以看到无论是否使用__block修饰符,在block内部和外部打印变量的地址是完全不相同的,地址的差别很大,而局部变量是存放在栈区的,可以推断后面的变量是在堆区域的.而在ARC下,当对Block进行赋值,系统会自动的将block拷贝的堆区,下面我们介绍Block的本质和分类之后再进行剖析.
Block的本质和分类
定义一个Block,通过NSLog进行打印
void (^globalBlock)() = ^{
NSLog(@"globalBlock");
};
NSLog(@"%@",globalBlock);
打印结果:<NSGlobalBlock: 0x1000020d0>,和打印对象的结果一样,前面是类名,后面是对象地址.
这是一个未捕获任何变量的Block,我们可以声明一个捕获变量的Block来查看结果是否一致
int num = 2;
void (^block)(void) = ^{
NSLog(@"%d",num);
};
NSLog(@"%@",block);
block();
打印结果:<NSMallocBlock: 0x1004028a0> 我们知道在ARC下,只要对Block进行赋值,就会将Block复制到堆上,那么我们定义一个不赋值的Block进行查看
int a = 8;
NSLog(@"%@",^{
NSLog(@"%d",a);
});
打印结果:<NSStackBlock: 0x7ffeefbff608>
通过打印结果我们可以猜测Block的本质就是一个对象,所属不同的类
事实胜于雄辩,我们通过编译器指令来查看底层转换代码来一探究竟
clang 是 Objective-C 的编译器前端,用它可以将 Objective-C 代码转换为 C/C++ 代码,然后可以来分析 Objective-C 中的一些特性是怎么实现的。
clang -rewrite-objc main.m -o main.cpp
指定架构模式进行转换
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc
void (^test)(void) = ^{
};
test();
转换后的代码,只提取关键部分
struct __block_impl {
//isa 指向block所属的类 ->block的本质是一个对象
void *isa;
//标识位
int Flags;
//预留字段
int Reserved;
//函数指针
void *FuncPtr;
};
struct __main_block_impl_0 {
//block实现
struct __block_impl impl;
//block描述
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;
}
};
//函数的实现 参数为创建block生成的结构体
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
}
static struct __main_block_desc_0 {
//预留字段
size_t reserved;
//大小
size_t Block_size;
}
//生成block_desc结构体对象
__main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
int main(int argc, const char * argv[]) {
//函数指针 调用构造函数,传入函数指针,block_desc对象,强转为函数指针类型
void (*test)(void) = (
(void (*)())
&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA
)
);
//调用函数的实现,传入test指针(实际为__main_block_impl_0类型)当做参数
((void (*)(__block_impl *))
((__block_impl *)test)->FuncPtr
)((__block_impl *)test);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
当block捕获局部变量时
int value = 10;
void (^test)(void) = ^{
NSLog(@"%d",value);
};
test();
转换后
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 value;
//构造函数 :后面是初始化列表 这里相当于value = _value;
//初始化类的成员有两种方式,一是使用初始化列表,二是在构造函数体内进行赋值操作。
//初始化列表性能更好,对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差别不是很大,但是对于类类型来说,使用初始化列表更加高效
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _value, int flags=0) : value(_value) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//取出变量值 打印
int value = __cself->value; // bound by copy
//NSLog....
}
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 value = 10;
void (*test)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, value));
((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
只是block内部多了一个捕获变量类型的成员变量.
总结:
_NSConcreteStackBlock:
只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。
StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了。_NSConcreteMallocBlock:
有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制_NSConcreteGlobalBlock:
没有用到外界变量或只用到全局变量、静态变量的block为_NSConcreteGlobalBlock,生命周期从创建到应用程序结束。
__block的实现原理
__block int a = 0;
void (^block)(void) = ^{
a++;
NSLog(@"%d",a);
};
block();
转换后的代码
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//__block
struct __Block_byref_a_0 {
void *__isa;
/**重要**/
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
//变量
int a;
};
struct __main_block_impl_0 {
//block
struct __block_impl impl;
//描述变量
struct __main_block_desc_0* Desc;
//__block修饰生成的结构体
__Block_byref_a_0 *a; // by ref
//构造函数
//a 的值是 a->__forwarding
__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
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cm_kv9bb84x62b8y9g8ml72bc3w0000gn_T_main_a5279b_mi_0,(a->__forwarding->a));
}
//Copy操作 第一个为目标对象 第二个参数为原来的对象
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*/);
}
//Dispose
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->a,
8/*BLOCK_FIELD_IS_BYREF*/);
}
//Block描述
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*);
}
//block描述的初始化方法
__main_block_desc_0_DATA = { 0,
//大小计算,仅仅是进行了sizeof
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定义一个变量,实际上就是生成了一个__Block_byref_xx_0类型的结构体变量
//结构体有5个成员变量。第一个是isa指针,第二个是指向自身类型的__forwarding指针,第三个是一个标记flag,第四个是它的大小,第五个是变量值,名字和变量名同名。
// 传入的参数分别是:
// isa: void *0;
// __forwarding: &a,即a结构体变量的地址
// __flags: 0
// __size: sizeof(结构体)
// a: 0,即a的值
//__forwarding指针初始化传递的是自己的地址
__attribute__((__blocks__(byref)))
__Block_byref_a_0 a = {
(void*)0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
0};
void (*block)(void);
//Block的定义
block = ((void (*)())&__main_block_impl_0(//函数实现
(void *)__main_block_func_0,
//描述block函数函数
&__main_block_desc_0_DATA,
//__block_byref__
(__Block_byref_a_0 *)&a,570425344)
);
//a++
(a.__forwarding->a)++;
//拿到FuncPtr函数指针,FuncPtr函数有一个参数,即传入的block 自身
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
///
栈上的Block会持有__block对象,把Block复制到堆上,堆上也会重新复制一份__block,并且该Block也会继续持有该__block。当Block释放的时候,__block没有被任何对象引用,也会被释放销毁。
__block变量是在栈空间,其forwarding指向自身,当变量从栈空间copy到堆空间时,原来栈空间的变量的_forwarding指向了新创建的变量(堆空间上),这其实就达到了从Objective C层面改变原变量的效果,这样不管__block怎么复制到堆上,还是在栈上,都可以通过(i->__forwarding->i)来访问到变量值。