Block 回调实现
不着急,先跟着我实现最简单的 Block 回调传参的使用,如果你能举一反三,基本上可以满足了 OC 中的开发需求。已经实现的同学可以跳到下一节。
首先解释一下我们例子要实现什么功能(其实是烂大街又最形象的例子):
有两个视图控制器 A 和 B,现在点击 A 上的按钮跳转到视图 B ,并在 B 中的textfield 输入字符串,点击 B 中的跳转按钮跳转回 A ,并将之前输入的字符串
显示在 A 中的 label 上。也就是说 A 视图中需要回调 B 视图中的数据。
想不明白的同学可以看一看最终实现的效果图:
block example
这里不再对 Block 的语法做说明了,不了解的同学可以点传送门。
首先,我们需要定义两个试图控制器 AViewController 和 BViewController,现在我们需要思考一下,Block 应该在哪里定义呢?
我们可以简单地这样思考,需要回调数据的是 A 视图,那么 Block 就应该在 B 中定义,用于获取传入回调数据。
因此我们在 BViewController.h 中定义如下:
//BViewController.h
#import <UIKit/UIKit.h>
typedef void(^CallBackBlcok) (NSString *text);//1
@interface BViewController : UIViewController
@property (nonatomic,copy)CallBackBlcok callBackBlock;//2
@end
void(^) (NSString *text)
的别名为 CallBackBlcok
。这样我们就可以在代码 2 中,使用这个别名定义一个 Block 类型的变量 callBackBlock
。callBackBlock
之后,我们可以在 B 中的点击事件中添加 callBackBlock
//BViewController.m
- (IBAction)click:(id)sender {
self.callBackBlock(_textField.text); //1
[self.navigationController popToRootViewControllerAnimated:YES];
}
这样我们就可以在想要获取数据回调的地方,也就 A 的视图中调用 block:
// AViewController.m
- (IBAction)push:(id)sender {
BViewController *bVC = [self.storyboard instantiateViewControllerWithIdentifier:@"BViewController"];
bVC.callBackBlock = ^(NSString *text){ // 1
NSLog(@"text is %@",text);
self.label.text = text;
};
[self.navigationController pushViewController:bVC animated:YES];
}
代码 1 中,通过对回调将 B 中的数据传递到代码块中,并赋值给 A
中的 label,实现了整个回调过程。
上例是通过将 block 直接赋值给 block 属性,也可以通过方法参数的方式传递 block 块。
由于考虑有的小伙伴翻墙比较困难,完整的示例代码放在 git.oschina.net 上,代码地址:BlockMagic 。
关于 Block 的疑惑
到目前为止,一切看起来都很美好(如果你照着上面的例子做的话),功能正常, A 视图中也获取到数据了。但是某些人可能就要说了,你的代码有问题,你的思路有问题,你这是误人子弟。
是的,代码的确还有问题,第一个问题就是循环引用的问题,在 A 视图的block 代码块中:
bVC.callBackBlock = ^(NSString *text){
NSLog(@"text is %@",text);
self.label.text = text;
};
self.label.text = text;
,在 Block 中引用 self ,也就是 A ,而 A 创建并引用了 B ,而 B 引用 callBackBlock
,此时就形成了一个循环引用,而编译器也不会报任何错误,我们需要非常小心这个问题(面试百分百问到我会乱说?)。此时我们通常的解决方法是使用弱引用来解除这个循环:
__weak AViewController *weakSelf = self;
bVC.callBackBlock = ^(NSString *text){
NSLog(@"text is %@",text);
// self.label.text = text;
weakSelf.label.text = text;
};
__block
修饰符),但是很明显 weakSelf.label.text
的值被修改了,并且没有用__block
修饰符, 这是为什么呢?因为 label
是个全局变量,而如果像如下的局部变量 a
局部变量
通过这个小例子发现的两个问题,也算是值得了。
Block 为什么能实现神奇的回调
在这里我不会说什么实现原理,仅仅是个人对 Block 能实现神奇回调的理解,有错误的地方请大家指出。
在先前使用 Block 的过程中,虽然会使用,但是总是有一个疑惑,简单说来就是:
为什么在 A 中的 block 块能调用到 B 中的数据?
self.callBackBlock(_textField.text)
_textField.text
事实是,通过简单的整理我们可以发现完整的回调流程应该是这样的:
回调流程
- block 代码块赋值给
bVC.callBackBlock
- ,此时
callBackBlock
- 调用
callBackBlock(NSString *text)
callBackBlock
callBackBlock
- 现在再通过一段代码可以更清晰地理解这个原理:
bVC.callBackBlock = ^(NSString *text){ //1
NSLog(@"text is %@",text);
};
bVC.callBackBlock = ^(NSString *text){ //2
NSLog(@"text b is %@",text);
};
callBackBlock
进行了两次赋值,结果会怎么样呢?
two block
callBackBlock
的指针最后指向了代码 2 的代码块。所以并没有什么神奇的魔法,也没什么隐藏的底层机制(这里指的是方便理解的底层)让你可以带着疑惑去使用它。