1.Block是什么
Block
是带有自动变量的匿名函数,是C语言的一个扩充功能。Block
本质上也是一个OC对象,内部也有一个isa指针,其内部封装了函数调用以及函数调用环境。
Block
的定义:
return_type:表示返回的对象/关键字等(可以是void,并省略)
blockName:表示block的名称
var_type:表示参数的类型(可以是void,并省略)
varName:表示参数名称
return_type (^blockName)(var_type) = ^return_type (var_type varName) {
// ...
};
typedef return_type (^BlockTypeName)(var_type);
@property(nonatomic, copy)return_type (^blockName) (var_type);
关于Block
的用法这里不介绍太多,这篇文章主要分析下Block
的原理。
二、不截获变量的Block
先来看一个最简单的Block
形式:
int(^blockName)(int, int) = ^int(int a, int b){
return a + b;
}
blockName(3, 5);
通过clang rewrite
命令方式改写后:
int(*blockName)(int, int) = ((int (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((int (*)(__block_impl *, int, int))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName, 3, 5);
我们省略掉类型转换后的代码是这样的:
int(*blockName)(int, int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
(blockName->FuncPtr)(blockName, 3, 5);
- 可以看出
blockName
其实就是__main_block_impl_0
类型的对象。 -
__main_block_impl_0
通过FuncPtr
调用该Block
。
首先先看下__block_impl
和__main_block_desc_0
这两个结构体:
// block_impl,保存着isa指针和函数地址
struct __block_impl{
void *isa; // isa指针
int Flags; // 标记位
int Reserved;
void *FuncPtr; //
}
// block描述
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)};
接下来是blockName
的实现,第一个参数为__main_block_impl_0
结构体:
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b){
return a +b;
}
最后__main_block_impl_0
将上面三个封装起来,就是一个自定义Block
了,__main_block_impl_0
的定义如下:
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;
}
};
通过__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA))
初始化之后,__main_block_impl_0
变成了这样:
{
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
}
调用Block
的时候最终调用到FuncPtr
对应的函数。
三、截获临时变量的Block
int c = 10;
static int d = 5;
int(^blockName)(int, int) = ^int(int a, int b){
NSLog(@"c:%d, d:%d", c, d);
return a + b;
}
blockName(3, 5);
这个Block
截获了临时变量c和静态变量d,看看不截获变量的Block
有何不一样。
首先还是用rewrite
以下:
int c = 10;
static int d = 5;
int(*blockName)(int, int) = ((int (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, c, &d, 0));
((int (*)(__block_impl *, int, int))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName, 3, 5);
这里可以看出初始化__main_block_func_0
的时候,c是传值,而d的则是引用的形式。
__main_block_impl_0
的定义:
struct __main_block_impl_0{
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int c;
int *d;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int *_d, int flags=0) : c(_c), d(_d){
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看到,结构体里面多了c和d,因为d是静态变量,是一直存在的,所以可以用指针的方式引用。
Block
的函数实现:
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b){
int c = __cself->c;
int *d = __cself->d;
NSLog(@"c:%d, d:%d", c, *d);
return a +b;
}
通过这个我们就明白了第一个参数的作用,用来获取__main_block_impl_0
里面的变量。
四、截获类对象的Block
截获对象的Block
:
TestClass *testClass = [TestClass new];
testClass.a = 1;
int(^blockName)(int, int) = ^int(int a, int b){
NSLog(@"class.a:%d", testClass.a);
return a + b;
}
blockName(3, 5);
rewrite
之后:
int(*blockName)(int, int) = ((int (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, testClass, 570425344));
((int (*)(__block_impl *, int, int))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName, 3, 5);
这里testClass
是传地址进去的,我们可以猜想Block
中也应该用指针引用了该对象。看下__main_block_func_0
的实现:
struct __main_block_impl_0{
struct __block_impl impl;
struct __main_block_desc_0* Desc;
TestClass *testClass;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, TestClass *_testClass, int flags=0) : testClass(_testClass){
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b){
TestClass *testClass = __cself->testClass;
NSLog(@"class.a:%d", testClass.a);
return a +b;
}
跟我们的猜测一样,这样的话Block
会把截获到的对象给引用起来,如果该对象有引用该Block
的话,那就会造成循环引用。截获到对象的时候,__main_block_desc_0
描述是有点不一样的:
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};
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->testClass, (void*)src->testClass, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->testClass, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
这里的__main_block_desc_0
多了copy
和dispose
两个函数指针,其作用是:
copy
和dispose
函数中传入的都是__main_block_impl_0
结构体本身。
copy
本质就是__main_block_copy_0
函数,__main_block_copy_0
函数内部调用_Block_object_assign
函数,_Block_object_assign
中传入的是TestClass
对象的地址,TestClass
对象,以及8,会将TestClass
对象强引用起来。
dispose
本质就是__main_block_dispose_0
函数,__main_block_dispose_0
函数内部调用_Block_object_dispose
函数,_Block_object_dispose
函数传入的参数是TestClass
对象,以及8,结束强引用。
五、截获__weak修饰的变量的Block
为了避免上述所说的循环引用问题,我们一般会在Block
传入weak
修饰的对象,那么通过weak
修饰的变量又是如何在Block
中存放的呢?
TestClass *testClass = [TestClass new];
testClass.a = 1;
__weak TestClass *weakClass = testClass;
int(^blockName)(int, int) = ^int(int a, int b){
NSLog(@"class.a:%d", weakClass.a);
return a + b;
}
blockName(3, 5);
通过clang rewrite
要加上架构才支持weak
修饰符,clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-11.0.0 main.m
,改写后的代码如下:
struct __main_block_impl_0{
struct __block_impl impl;
struct __main_block_desc_0* Desc;
TestClass *__weak weakClass;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, TestClass *__weak _weakClass, int flags=0) : weakClass(_weakClass){
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b){
TestClass *__weak weakClass = __cself->weakClass;
NSLog(@"class.a:%d", weakClass.a);
return a +b;
}
可以看到,__main_block_impl_0
和__main_block_func_0
中都是以weak
修饰符形式存在的,这样就不会强引用起来,也能解决循环引用的问题。
六、截获__block修饰的变量的Block
最后来看下__block
修饰符,我们知道通过__block
修饰后,可以更改截获到的变量的值。
__block int c = 1;
__weak TestClass *weakClass = testClass;
int(^blockName)(int, int) = ^int(int a, int b){
c = 2;
return a + b;
}
blockName(3, 5);
// clang重写后
__attribute__((__blocks__(byref))) __Block_byref_c_0 c = {(void *)0, (__Block_byref_c_0 *)&c, 0, sizeof(__Block_byref_c_0), 1};
int(*blockName)(int, int) = ((int (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_c_0 *)&c, 570425344));
((int (*)(__block_impl *, int, int))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName, 3, 5);
我们定义了__block int c = 1
,但通过编译器改写后,变成了__Block_byref_c_0 c = {0, (__Block_byref_c_0 *)&c, 0, sizeof(__Block_byref_c_0), 1};
,这里面的__Block_byref_c_0
定义如下:
struct __Block_byref_c_0{
void *__isa;
__Block_byref_c_0 *__forwarding // 初始化默认指向自己;
int __flags;
int __size;
int c;
};
__main_block_impl_0
的定义以及Block
函数:
struct __main_block_impl_0{
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_c_0 *c;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_c_0 *_c, int flags=0) : c(_c->__forwarding){
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b){
__Block_byref_c_0 *c = __cself->c;
(c->__forwarding->c) = 2;
return a +b;
}
通过__block
修饰后,c已经不是一个变量了,而是一个__Block_byref_c_0
对象,对象中保存了这个值,修改的话也是通过这个对象进行修改。