一、block基础
1、什么是block?
作者的原话:带有自动变量的匿名函数。
在这里解释一下两个词,第一个词:自动变量,在执行block的时候如果它的代码块包含局部变量它会自动截取该变量,下文会举例子说明。第二个词:匿名函数,该词说明了block的具体的作用相当于一个函数(OC中的方法),但是你不用为它命名。
首先,block是OC中的一个对象,所以结合上面的理解block的本质就是一个存储代码段的OC对象。
2、block的语法
2.1、语法结构
返回值类型 (^blockName)(参数类型) = ^(参数列表){
body;
};
eg:
int (^firstBlock)(int,int)= ^(int x,int y){
return x + y;
};
2.2、block的三种情况
1)无返回值无参数
- (void)firstFunc{
void (^firstBlock)() = ^{
NSLog(@"无参数无返回值");
};
firstBlock();
}
2)无返回值有参数
- (void)secondFunc{
void (^secondBlock)(int) = ^(int x){
NSLog(@"有参数无返回值.%d",x);
};
secondBlock(666);
}
3)有返回值有参数
- (void)thirdFunc{
int (^thirdBlock)(int, int) = ^(int x,int y){
NSLog(@"有参数,有返回值");
return x + y;
};
int total = thirdBlock(2,3);
NSLog(@"total = %d",total);
}
3、block最常用的方式:控制器反向传值(eg:注册界面将信息返回给登录界面)
具体步骤:
1)在A控制器实现B控制器定义的block(如果是值从B传到A)
__weak ViewController *weakSelf = self;
secondVC.deliverStrBlock = ^ (NSString *deliverStr) {
weakSelf.testStr = deliverStr;
NSLog(@"%@",weakSelf.testStr);
};
2)在B的.h文件声明block
typedef void (^DeliverStrBlock) (NSString *str);
@property (nonatomic, copy) DeliverStrBlock deliverStrBlock;
3)在B的.m文件调用block
实例代码:
if (self.deliverStrBlock) {
self.deliverStrBlock(@"test str");
}
二、block进阶
1、如何在block中改变局部变量的值?
NSString *a = @"5";
int (^thirdBlock)(int, int) = ^(int x,int y){
a = @"haha";
NSLog(@"有参数,有返回值 %@",a);
return x + y;
};
如果你在代码中写了上述代码,编译器会报错,错误提示为:你不能分配该变量的值缺少__block
如果我们想搞清楚为什么会报这个错误就要从block的源码上分析了,在高级编程这本书中,作者提到在block截取变量的时候回自动生成一个同名的变量,并将截取的值赋给该变量,所以此a非彼a,block中的a并不会指向代码段外的a的内存地址,所以你改变此值并不能改变block外的变量a的值,因此,编译器才会报错。
但是当你在NSString 前面加上__block
的前缀时,该代码则会正常运行。
代码示例:
__block NSString *a = @"5";
int (^thirdBlock)(int, int) = ^(int x,int y){
a = @"haha";
return x + y;
};
那么问题来了,为什么在a的前面加上前缀修饰符__block
该代码就能正常运行呢?要想解决该问题还要去分析block的源码。在加上__block
的修饰符,在block的生成结构体实例的时候,__block
也会生成一个结构体实例,并且在该结构体中有一个__forwording
的指针指向截取的变量的内存地址,而block的__main_block_impl_0
结构体实例持有指向__block变量
的__Block_byref_val_0
结构体实例的指针,所以block可以拥有指向截取变量内存地址的指针。
所以当添加了__block
之后再block中就可以修改变量的值。
2、如何避免循环引用
这个问题应该是面试或者实际情况中遇到最多的一个问题了。
解决循环引用的两个方法
1)使用了__weak,如果在上述代码中不将控制器弱引用block的话就会发生循环引用而无法释放的问题。(A控制器拥有block,而且block拥有A)。
__weak ViewController *weakSelf = self;
2)使用__block
(self拥有block,block拥有__block
,__block
拥有self)
注意:此方式必须执行一次block。即执行tmp = nil,来打断循环链。
__block id tmp = self;
secondVC.deliverStrBlock = ^ (NSString *deliverStr) {
NSLog(@"%@",tmp);
tmp = nil;
};
3、使用typedef定义block
如果你也觉得block的语法结构很反人类,你可以使用typedef来重命名block
typedef void (^DeliverStrBlock) (NSString *str);
@property (nonatomic, copy) DeliverStrBlock deliverStrBlock;
4、结束语
在一开始读《Objective-C高级编程》这本书的时候,也是一脑的懵逼,但是当静下心来的时候多读几遍这本书就会对block有很深的认识。推荐大家多读几遍,本文中有写的不对或理解有偏差的地方欢迎大家指正