一、什么是Block ?
Block 是将函数及其执行上下文封装起来的对象
比如:
- (void)blockTest1 {
int num = 3;
int(^myBlock)(int n) = ^int(int n){
return num *n;
};
num = 1;
myBlock(2);
}
将Objective-C代码转换为C\C++代码
指令如下
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
通过 clang -rewrite-objc SWTestVC.m 命令编译该.m 文件
blockTest1会被编译成如下形式:
static void _I_SWTestVC_blockTest1(SWTestVC * self, SEL _cmd) {
int num = 3;
int(*myBlock)(int n) = ((int (*)(int))&__SWTestVC__blockTest1_block_impl_0((void *)__SWTestVC__blockTest1_block_func_0, &__SWTestVC__blockTest1_block_desc_0_DATA, num));
num = 1;
((int (*)(__block_impl *, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 2);
}
SWTestVC 是文件名,blockTest1 是方法名,可以忽略
SWTestVC__blockTest1_block_impl_0 的结构体为
struct __SWTestVC__blockTest1_block_impl_0 {
struct __block_impl impl;
struct __SWTestVC__blockTest1_block_desc_0* Desc;
int num;
__SWTestVC__blockTest1_block_impl_0(void *fp, struct __SWTestVC__blockTest1_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
_block_impl 结构体为
struct __block_impl {
void *isa; // isa指针
int Flags;
int Reserved;
void *FuncPtr; // 函数指针
};
block 内部有 isa 指针,所以说其本质也是 OC 对象
block内部,也就是 __SWTestVC__blockTest1_block_func_0
如下
static int __SWTestVC__blockTest1_block_func_0(struct __SWTestVC__blockTest1_block_impl_0 *__cself, int n) {
int num = __cself->num; // bound by copy
return num *n;
}
二、Block 变量截获
- 局部变量截获 是值截获
比如:
- (void)blockTest1 {
int num = 3;
int(^myBlock)(int n) = ^int(int n){
return num *n;
};
num = 1;
NSLog(@"%d",myBlock(2));
}
这里的输出是 6 而不是 2,原因就是对局部变量 num 的截获是值截获。 同样,在 block 里如果修改变量 num,也是无效的,甚至编译器会报错
- (void)blockTest2 {
NSMutableArray * arr = [[NSMutableArray alloc] initWithObjects:@"1",@"2", nil];
void (^myBlock2)(void) = ^{
NSLog(@"%@",arr);
};
[arr addObject:@"3"];
myBlock2();
}
这里打印的是 1,2,3,而不是 1,2
对于局部对象而言,也是截获值,而不是指针,在外部将其置为 nil,对 block 没有影响,而该对象调 用方法会影响~
也就是说 block创建时,会捕获指针arr的值,也就arr所指向的内存的内容,数组的addObject操作,会改变这块内存的内容,但arr = nil,是对指针操作,指针不再指向这块内存,不会对这块内存的内容产生影响~
思考:
- (void)blockTest5 {
NSMutableArray * arr = [[NSMutableArray alloc] initWithObjects:@"1",@"2", nil];
void (^myBlock5)(void) = ^{
NSLog(@"%@",arr); // 局部变量
[arr addObject:@"4"];
};
[arr addObject:@"6"];
NSMutableArray * tempArray = arr;
arr = [[NSMutableArray alloc] initWithObjects:@"4",@"5", nil];
[arr addObject:@"3"];
[tempArray addObject:@"7"];
arr = nil;
myBlock5();
}
输出结果 : 1,2,6,7
- 局部静态变量截获,是指针截获
- (void)blockTest6{
static int m = 3;
int (^myBlock6)(int) = ^ int(int n){
return m*n;
};
m = 1;
NSLog(@"%d",myBlock6(2));
}
输出为 2,意味着 m = 1 这里的修改 m 值是有效的,即是指针截获。 同样,在 block 里去修改变量 m,也是有效的。
- (void)blockTest7 {
static NSArray * arr = nil;
arr = [[NSArray alloc] initWithObjects:@"1",@"2", nil];
void (^myBlock7)(void) = ^{
NSLog(@"blockTest7 %@",arr); // 局部变量
};
arr = @[@"1",@"2",@"3"];
// arr = nil;
myBlock7();
}
输出 1,2,3
局部对象变量,也是指针截获
- 全局变量,静态全局变量截获:不截获,直接取值
我们同样用 clang 编译看下结果
static NSInteger num3 = 300;
NSInteger num4 = 3000;
- (void)blockTest3 {
NSInteger num = 30;
static NSInteger num2 = 3;
__block NSInteger num5 = 30000;
void(^myBlock3)(void) = ^{
NSLog(@"%zd",num); // 局部变量
NSLog(@"%zd",num2); // 静态变量
NSLog(@"%zd",num3); // 静态全局变量
NSLog(@"%zd",num4); // 全局变量
NSLog(@"%zd",num5); // __block修饰变量
};
myBlock3();
}
编译后
struct __SWTestVC__blockTest3_block_impl_0 {
struct __block_impl impl;
struct __SWTestVC__blockTest3_block_desc_0* Desc;
NSInteger num; // 局部变量
NSInteger *num2; // 静态变量
__Block_byref_num5_0 *num5; // by ref // __block 变量
__SWTestVC__blockTest3_block_impl_0(void *fp, struct __SWTestVC__blockTest3_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;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看到局部变量被编译成值形式,而静态变量被编成指针形式,全局变量并未截获。而__block 修饰的变 量也是以指针形式截获的,并且生成了一个新的结构体对象:
struct __Block_byref_num5_0 {
void *__isa;
__Block_byref_num5_0 *__forwarding;
int __flags;
int __size;
NSInteger num5;
};
该对象有个属性:num5,即我们用__block 修饰的变量。 这里__forwarding 是指向自身的(栈 block)。 一般情况下,如果我们要对 block 截获的局部变量进行赋值操作需添加__block 修饰符,而对全局变量,静态变量是不需要添加__block 修饰符的。
另外,block 里访问 self 或成员变量都会去截获 self。
- (void)blockTest8 {
self.t = 2;
self.h = 3;
void (^myBlock8)(void) = ^ void (void){
NSLog(@"blockTest8 %d %d",self.t,self.h);
};
self.t = 5;
myBlock8();
输出 5,3
三、Block的几种形式
分为全局Block(_NSConcreteGlobalBlock)、栈Block(_NSConcreteStackBlock)、堆Block(_NSConcreteMallocBlock)三种形式,其中栈Block存储在栈(stack)区,堆Block存储在堆(heap)区,全局Block存储在已初始化数据(.data)区
- 不使用外部变量的Block是全局Block
比如:
- (void)blockTest11 {
NSLog(@"%@",[^{NSLog(@"globalBlock");} class]);
}
输出:NSGlobalBlock
- 使用外部变量并且未进行 copy 操作的 block 是栈 block
比如:
- (void)blockTest12 {
int m = 12;
NSLog(@"%@", [^{NSLog(@"stackBlock %d",m);} class]);
}
输出:NSStackBlock
日常开发常用于这种情况:
- (void)blockTest15 {
[self testWithBlock:^{
NSLog(@"blockTest15 %@",self);
}];
}
- (void)testWithBlock:(void(^)(void))block {
block();
NSLog(@"%@",[block class]);
}
输出: NSStackBlock
- 对栈Block进行Copy操作,就是堆Block,对全局Block进行Copy操作,还是全局Block
比如 对1中的全局block进行copy操作,即赋值:
- (void)blockTest13 {
void (^myBlock13)(void) = ^{
NSLog(@"globalBlock");
};
NSLog(@"%@",[myBlock13 class]);
}
输出: NSGlobalBlock
仍是全局Block
而对 2 中的栈 block 进行赋值操作:
- (void)blockTest14 {
int m = 12;
void (^myBlock)(void) = ^ {
NSLog(@"mallocBlock %d",m);
};
NSLog(@"%@",[myBlock class]);
}
输出:NSMallocBlock
是堆Block
对栈Block Copy后,并不代表栈Block消失了,左边的Block是堆Block,右边被Copy的Block还是栈Block
比如:
- (void)blockTest16 {
[self testWithBlock2:^{
NSLog(@"%@",self);
}];
}
- (void)testWithBlock2:(void(^)(void))block {
block();
void (^tempBlock)(void) = block;
NSLog(@"%@ %@",[tempBlock class],[block class]);
}
输出:NSMallocBlock NSStackBlock
即如果对栈 Block 进行 copy,将会 copy 到堆区,对堆 Block 进行 copy,将会增加引用计数,对全局 Block 进行 copy,因为是已经初始化的,所以什么也不做。
另外,__block 变量在 copy 时,由于__forwarding 的存在,栈上的__forwarding 指针会指向堆上的__forwarding 变量,而堆上的__forwarding 指针指向其自身,所以,如果对__block 的修改,实际上是在修改堆上的__block 变量。
即__forwarding 指针存在的意义就是,无论在任何内存位置,都可以顺利地访问同一个__block 变量。
另外由于 block 捕获的__block 修饰的变量会去持有变量,那么如果用__block 修饰 self,且 self 持有 block,并且 block 内部使用到__block 修饰的 self 时,就会造成多循环引用,即 self 持有 block,block 持 有__block 变量,而__block 变量持有 self,造成内存泄漏。
比如:
- (void)blockTest17 {
__block typeof(self) weakSelf = self;
self.testBlock = ^{
NSLog(@"%@",weakSelf);
};
self.testBlock();
}
如果要解决这种循环引用,可以主动断开__block 变量对 self 的持有,即在 block 内部使用完 weakself 后, 将其置为 nil,但这种方式有个问题,如果 block 一直不被调用,那么循环引用将一直存在。 所以,我们最好还是用__weak 来修饰 self
- (void)blockTest18 {
__weak typeof(self) weakSelf = self;
self.testBlock = ^{
__strong typeof(self) self = weakSelf;
NSLog(@"%@",self);
};
self.testBlock();
}
网友评论