iOS Block解析
Block可表示为 " 带有自动变量值的匿名函数 "
1. 为什么是匿名函数
在C语言中, 函数必须要有函数名, 如
void funcName(int a) {
printf("%d\n", a);
}
由上面的代码可见, 不知道函数名就无法调用函数.
但是Block就不一样, 不需要函数名
^ (int a) {
printf("%d\n", a);
}
2. 为什么叫做 " 带有自动变量值 "
2.1" 带有自动变量值 "
在Block中表现为 " 截获自动变量值 "
看个例子
int a = 10;
void (^blk)() = ^ {
printf("%d\n",a);
};
a ++;
blk();
输出结果: 10
为什么输出的结果是10, 而不是11? 这就是Block的截获自动变量值, 这么做的原因是: 防止a被释放后, Block中的a就没有值了; 将a的值 " 截获 " 下来, 这样无论Block在什么时候调用, 都不会出现a被释放的情况. 我们利用clang命令看看Block的底层实现
int main() {
int a = 10;
void (*blk)() = (__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));
a ++;
((*blk)->FuncPtr)(blk);
}
从最后一句((blk)->FuncPtr)(blk);
看到, blk
调用了FuncPtr
函数, 那这个FuncPtr
是什么? 看下面的代码
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;
}
};
FuncPtr
是来自fp
, fp
又是__main_block_impl_0
的第一个参数, 而我们在main函数
中看到, __main_block_impl_0
的第一个参数是__main_block_func_0
, 也就是说 FuncPtr
就是__main_block_func_0
, 接着我们看看__main_block_func_0
是什么样的
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("%d\n",a);
}
int a = __cself->a; // bound by copy
我们发现, 这里的a是拷贝过来的, 所以后面的a++
对Block中的a
没有影响
2.2 __block关键字
说到Block外的变量问题, 不得不说说__block
关键字, 要想在Block中重新对Block外的变量赋值, 必须要在Block外的变量前加上__block
, 这是为什么呢? 我们看个例子
int main() {
__block int a = 10;
void (^blk)() = ^ {
printf("%d\n",a);
};
a ++;
blk();
}
输出结果是: 11
还是通过clang来查看底层的代码, 我们发现, 此时的a不再是int类型了, 而是一个结构体__Block_byref_a_0
int main() {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {0,&a, 0, sizeof(__Block_byref_a_0), 10};
void (*blk)() = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &a, 570425344));
(a.__forwarding->a) ++;
((blk)->FuncPtr)(blk);
}
我们来看看这个结构体的信息
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
其中, __forwarding
也是一个__Block_byref_a_0
的结构体指针, 指向哪儿暂时不看; 最后有个int a
, 记住这个 a !!!!!
看看main函数有什么变化
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;
}
};
原来的int a
变成了 __Block_byref_a_0 *a; // by ref
, block的构造函数中的a也变成了结构体指针
最后, 我们还是看看__main_block_func_0
方法
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
printf("%d\n",(a->__forwarding->a));
}
__Block_byref_a_0 *a = __cself->a; // bound by ref
表明, 这里的a不再是bound by copy
了, 而是bound by ref
, 是引用, 所以我们在Block调用前执行a++
是会引起Block中的a发生变化的;
这里的__forwarding
应该是指向结构体a自己, 第一个a就是结构体a, 最后的a是 int a
, 是具体的值
2.3 截获Objective-C对象
以NSMutableArray为例
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"1", @"2", nil];
void (^blk)(void) = ^ {
[arr insertObject:@"3" atIndex:2];
};
blk();
这样是可以的, 只是对可变数组进行修改, 而不是重新赋值
下面我们看一下对Objective-c对象重新赋值
![](https://img.haomeiwen.com/i5526464/bbacc28e40b307df.png)
报错, 加上
__block
关键字后即可正常使用