参考文章
ibireme的博客文章
iOS 高级编程
本文属于原创,转载请注明出处
简介
- Block截获自动变量相当于截获了变量的值,之后在Block外改变变量的值不会影响Block中的值
- Block中截获的自动变量不能修改,修改会报错,修改要给变量加__block修饰符,在Block调用后,原变量的值也会改变,但被__block修饰符修饰的变量已经不是单纯的变量了
- 截获并更改对象不会报错,赋值会报错,了解引用类型就知道原因,这里不再赘述
Block的实质
int main()
{
void (^blk)(void) = ^ { printf("Block\n"); };
blk();
return 0;
}
通过以下规则
- 根据Block所属的函数名(此处为main),和该Block在该函数出现的顺序值,此处为0,来给Clang变换的函数命名
- 此处的__cself相当于self,为指向当前Block的变量
以上代码被转为
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");
}
int main()
{
void (*blk)(void) = (void(*)void)&__main_block_impl_0(void*)__main_block_func_0, &__main_block_desc_0_DATA);
((void (*) (struct __block_impl *))(struct __block_impl *) blk);
return 0;
}
几个重要的结构体和函数
- __block_impl:为Block的结构体,存放的isa指针指向当前对象,FuncPtr指向当前Block保存的实现函数
- __main_block_desc_0:保存了Block的状态版本信息
- __main_block_func_0:保存了当前Block所实现的函数
- __main_block_impl_0:封装了Block的结构体和装有状态信息的__main_block_desc_0,提供一个构造函数
以上生成和调用Block的过程可以被概述为:
- 定义Block变量相当于调用__main_block_impl_0的构造函数,通过静态函数指针__main_block_func_0和结构体指针__main_block_desc_0初始化
- 在构造函数内部,将传递进来的__main_block_func_0函数使用isa为Block结构体中的成员变量FuncPtr初始化
- 调用Block时,使用isa指针取出Block中的FuncPtr函数进行调用
截获自动变量
int main()
{
int val = 10;
void (^blk)(void) = ^ { printf("val"); };
blk()
}
该源码中Block函数部分相当于
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val;
}
// __main_block_impl_0中的初始化如下
{
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
val = 10;
}
// 调用Block的函数时截获变量相当于
static void __main_block_func_0(struct __main_block_impl_0 *cself)
{
int val = cself->val;
printf(val);
}
- 截获变量时相当于执行Block语法时,Block语法所用的自动变量的值被保存到了Block的结构体实例里
__block说明符
- __block的全称为:__block存储域类说明符(__block storage-class-specifier)
struct __Block_byref_val_0 {
void *__isa;
__block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
// 没写出来的和上面相同
__Block_byref_val_0 *val;
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
__Block_byref_val_0 *val = __cself->val;
(val->__forwarding->val) = 1;
}
int main()
{
__Block_byref_val_0 val = {0, &val, 0, sizeof(__Block_byref_val_0), 10};
blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
return 0;
}
以上源码可以总结为:
- __block变成了结构体实例__Block_byref_val_0的结构体类型实例变量,并且被修饰的变量出现在该结构体里
- __main_block_impl_0里持有指向__block变量的__Block_byref_val_0结构体实例指针
- 通过__forwarding指针访问该结构体里的成员变量val
- 在这里使用__block修饰的变量可以通过改变结构体指针改变该结构体成员变量的值
Block 的 Copy
- Block中的isa指针是指向其Class的,在Runtime中定义了以下几种类:
- _NSConcreteStackBlock
- _NSConcreteGlobalBlock
- _NSConcreteMallocBlock
typedef int (^blk_t)(int);
blk_t func(int rate)
{
return ^(int count) { return rate * count; };
}
blk_t func(int rate)
{
blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
tmp = objc_retainBlock(tmp);
return objc_autoreleaseReturnValue(tmp);
}
// objc_retainBlock函数实际上就是__Block_copy函数
{
// 将栈上的Block复制到堆上
// 将堆上的地址作为指针赋值给变量tmp
tmp = __Block_copy(tmp);
return objc_autoreleaseReturnValue(tmp);
}
- __Block变量会随着Block变量被复制到堆上,在栈上的__Block变量被Block变量使用,复制到堆上后被Block变量持有,增加__Block变量的引用计数
{
__block int val = 0;
void (^blk)(void) = [^{ ++ val; }];
++ val;
blk();
}
// 无论是
^{ ++val; }
// 还是
++val;
// 都被转为
++ (val.__forwarding->val);
- 在变换Blcok语法的函数中,该变量val为复制到堆上的__Block变量结构体实例,而用Block无关的变量val为复制前栈上的__block结构体实例
- 但栈上的__block变量结构体实例在__block变量从栈复制到堆上时,会将成员变量__forwarding的值复制为目标堆上的__block变量的结构体实例地址
- 所以无论__block结构体无论在Block语法外还是语法中使用(栈上或堆上),都能正确访问
截获对象
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id blk) {
[array addObject: obj];
NSLog(@"%ld", [array count]);
} copy];
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id __strong array;
};
// 相当于retain
static void __main_block_copy_0(struct __main_block_impl_0 dst, struct __main_block_impl_0 *src)
{
_Block_object_assign(&dst->array, src->array, BLOCK_FIELD_IS_OBJECT);
}
// 相当于release
static void _main_block_dispose(struct __main_block_impl_0 *src)
{
_Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT);
}
- Block从栈复制到堆上调用copy和dispose函数,
- 调用Block的copy实例方法时
- Blcok作为函数返回值返回时
- 将Block赋值给__strong修饰符id类型的类或Block类型成员变量时
- 在方法名中含有usingBlock的Cocoa框架方法或GCD的API传递Block时
Block的引用循环
使用 __weak 和 __Block可以解开引用循环,不过使用__Block会有限制