什么是 Block?
Block 是将函数及其执行上下文封装起来的对象(结构体内含有 isa
指针)。比如:
void blockFunc2() {
__block int num = 100;
void (^block)() = ^{
NSLog(@"num equal %d", num);
};
num = 200;
block();
}
对应C语言的实现:
void blockFunc2() {
__attribute__ __Block_byref_num_0 num = {0, &num, 0, sizeof(__Block_byref_num_0 ), 100};
//通过指针运算符"&"获取地址,并赋值给block函数(构造函数并没有返回值,使用"&"得到的是什么地址???)
void (*block)() = &__blockFunc2_block_impl_0(__blockFunc2_block_func_0,
&__blockFunc2_block_desc_0_DATA,
&num,
570425344);
(num.__forwarding->num) = 200;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
其中:
__blockFunc2_block_impl_0()
是结构体 __blockFunc2_block_impl_0
的构造函数
struct __blockFunc2_block_impl_0 {
struct __block_impl impl;
struct __blockFunc2_block_desc_0 *Desc;
__Block_byref_num_0 *num;
__blockFunc2_block_impl_0(void *fp,
struct __blockFunc2_block_desc_0 *desc,
__Block_byref_num_0 *_num,
int flags=0) : num(_num -> __forwarding) //初始化列表 {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}
__blockFunc2_block_func_0
是 block
内部代码对应生成的函数
static void __blockFunc2_block_func_0(struct __blockFunc2_block_impl_0 *__cself) {
__Block_byref_num_0 *num = __cself -> num;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1w_8y5tf7_14hd5sk7_cm5sygr00000gn_T_main_ec62c2_mi_0,
(num -> __forwarding -> num));
}
__block_impl
为结构体
struct __block_impl {
void *isa; //isa指针,所以说block是对象
int Flags;
int Reserved;
void *FuncPtr; //函数指针
}
Block的变量截获
局部变量
局部变量截获是值截获,比如:
NSInteger num = 3;
NSInteger (^block)(NSInteger) = ^NSInteger(NSInteger n) {
return n * num;
};
num = 1;
NSLog(@"%zd", block(2));
这里的结果是6,而不是2,原因就是对局部变量 num
的截获是值截获。同样的,在 block
里修改变量 num
也是无效的,甚至编译器会报错。
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"1", @"2", nil];
void (^block)(void) = ^{
NSLog(@"%@", arr); //局部变量
[arr addObject:@"4"];
};
[arr addObject:@"3"];
arr = nil;
block();
这里的结果为1,2,3,arr
存储的是 NSMutableArray
对象的地址,将 arr
的值置为 nil
对 block
没有影响。
局部静态变量
局部静态变量截获是指针截获,比如:
static NSInteger num = 3;
NSInteger (^block)(NSInteger) = ^NSInteger(NSInteger n) {
return n * num;
}
num = 1;
NSLog(@"%zd", block(2));
结果为2,代码 num = 1;
是有效的,即是指针截获。同样的,在 block
里去修改变量 num
也是有效的。
全局变量和全局静态变量
Block不截获全局变量和全局静态变量,而是直接取值。
NSInteger num3 = 300;
static NSInteger num4 = 3000;
-(void)blockTest {
NSInteger num = 30;
static NSInteger num2 = 3;
__block NSInteger num5 = 30000;
void (^block)(void) = ^{
NSLog(@"%zd", num); //局部变量
NSLog(@"%zd", num2); //局部静态变量
NSLog(@"%zd", num3); //全局变量
NSLog(@"%zd", num4); //全局静态变量
NSLog(@"%zd", num5); //__block修饰的变量
};
block();
}
转换成C语言后:
struct __WYTest__blockTest_block_impl_0 {
struct __block_impl impl;
struct __WYTest__blockTest_block_desc_0 *Desc;
NSInteger num; //局部变量
NSInteger *num2; //局部静态变量
__Block_byref_num5_0 *num5; //__block修饰的变量
__WYTest__blockTest_block_impl_0(void *fp,
struct __
WYTest__blockTest_block_desc_0 *desc,
NSInteger _num,
NSInteger
*_num2,
__Block_byref_num5_0 *_num5,
int flags=0) : num(_num),
num2(_num2), num5(_num5 -> __forwarding) {
impl.isa = &_NSConcreteStackBlock;
//这是个栈block
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
另外,block
里访问 self
或成员变量都会去截获 self
。
__block的实现
__block
修饰的变量会被转换成结构体,比如上面代码中的:
__block int num = 100;
会被转换成
__attribute__ __Block_byref_num_0 num = {0, &num, 0, sizeof(__Block_byref_num_0 ), 100};
其中 __Block_byref_num_0
结构体的结构为:
struct __Block_byref_num_0 {
void *__isa; // isa指针
__Block_byref_num_0 *__forwarding; // 实例本身
int __flags;
int __size;
int num; // 我们的num值,该成员由代码决定
};
为什么需要多一个__forwarding,而不是直接获取结构体内的值呢???下文讲解
如果之后执行了以下代码:
num = 200;
则相当于为其成员num赋值:
(num.__forwarding->num) = 200;
Block的几种形式
Block分为全局Block(_NSConcreteGlobalBlock)、栈Block(_NSConcreteStackBlock)、堆Block(_NSConcreteMallocBlock)三种形式,其中栈Block存储在栈(stack)区,堆Block存储在堆(heap)区,全局Block存储在已初始化数据(.data)区。
- 不使用外部变量的
block
是全局Block
NSLog(@"%@", [^{
NSLog(@"globalBlock");
} class]);
输出:
__NSGlobalBlock__
- 使用外部变量并且未进行
copy
操作的block
是栈Block
NSInteger num = 10;
NSLog(@"%@", [^{
NSLog(@"stackBlock:%zd", num);
} class]);
输出:
__NSStackBlock__
日常开发常用于这种情况:
[self testWithBlock:^{
NSLog(@"%@", self);
}];
-(void)testWithBlock:(dispatch_block_t)block {
block();
NSLog(@"%@", [block class]);
}
- 对栈Block进行
copy
操作,就是堆Block;而对全局Block进行copy
,仍是全局Block
NSInteger num = 10;
void (^mallocBlock)(void) = ^{
NSLog(@"stackBlock:%zd", num);
}; //编译器自动进行copy
NSLog(@"%@", [mallocBlock class]);
输出:
__NSMallocBlock
- 如果对栈Block进行
copy
,将会copy到堆区;如果对堆Block进行copy
,将会增加引用计数。 - 注意循环引用
另外,__block
变量在 copy
时,由于 __forwarding
的存在,栈上的 __forwarding
指针会指向堆上的 __forwarding
变量,而堆上的 __forwarding
指针指向其自身。所以,如果对 __block
的修改,实际上是在修改堆上的 __block
变量。
即 __forwarding
指针存在的意义是无论在任何内存位置,都可以顺利访问同一个 __block
变量。