深入理解Block
2020-06-22 本文已影响0人
juriau
目录
- 1.block的内部结构
- 2.捕获变量
- 3.block的类型
- 3.循环引用
一、block的内部结构

block本质上也是个OC对象,它内部也有个isa指针。
二、捕获变量
block捕获变量的规则如下:

Q:block如何捕获变量?
- 对于自动变量的捕获:会在block结构体中增加一个同名变量,将自动变量的值赋给它;
- 对于静态变量的捕获:会在block结构体中增加一个同名指针变量,将自动变量的地址赋给它。
- 对于全局变量:不截获,直接访问。
三、block的类型

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上:
- 1.block作为函数返回值时
- 2.将block赋值给__strong指针时
- 3.block作为Cocoa API中方法名含有usingBlock的方法参数时
- 4.block作为GCD API的方法参数时
每一种类型的block调用copy后的结果:

Q:下面block分别属于什么类型? / 下面的输出是什么?
void (^block1)(void) = ^{};
static int static_global_val = 1;
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int static_val = 3;
int num = 4;
void (^block2)(void) = ^{ static_global_val = 0; };
void (^block3)(void) = ^{ static_val = 0; };
void (^block4)(void) = ^{};
void (^block5)(void) = ^{int a = num;};
void (^block6)(void) = [block1 copy];
void (^block7)(void) = [^{int a = num;} copy];
NSLog(@"%@\n", block1); // 全局block
NSLog(@"%@\n", block2); // 全局block
NSLog(@"%@\n", block3); // 全局block
NSLog(@"%@\n", block4); // 全局block
NSLog(@"%@\n", block5); // 堆block
NSLog(@"%@\n", block6); // 全局block
NSLog(@"%@\n", block7); // 堆block
NSLog(@"%@\n", ^{int a = num;}); // 堆block
}
return 0;
}
三、Q:如何使被截获的自动变量能被修改?A:__block变量
3.1 基本作用
如果想要可以修改捕捉的自动变量,那么需要将其设置为__block变量。下面来演示一下。
源程序
int main(int argc, const char * argv[]) {
__block int age = 10;
void (^block)(void) = ^{
age = 20;
};
block();
NSLog(@"%d", age);
}
编译后
int main(int argc, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_by_7ktpgnl143l_zkyqrb1mc_r00000gn_T_main_188616_mi_0, (age.__forwarding->age));
}
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
可以看到__block变量被包装为一个OC对象。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // 引用传递
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__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_age_0 *age = __cself->age;
(age->__forwarding->age) = 20;
}
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};
可以看到__block变量会被包装为一个对象:


3.2 __forwarding指针
看这样一个例子:
__block int val = 0;
void (^blk)(void) = [^{++val;} copy];
++val;
blk();
nslog(@"%d", val);
调用copy方法,会将block变量连同__block变量都复制到堆上。
这样的情况下,++val;
调用的是栈上的__block变量,而^{++val;}
调用的是堆上的__block变量,正是因为__forwarding指针才将它们指向正确的位置。
