Block回调
我们在开发中常常用到函数回调,你可以用通知来代替回调,但是大多数时候回调还是比通知方便。
A 简单两个界面之间的传值
1 在A界面 一个 label 一个 button
2 点击button跳转到B界面
3 在B界面 的textField输入内容 ,单击按钮返回。并将值传递给A界面
4 A界面的label接受传递的值 并且显示。
我用的代码布局还需要在AppDelegate.h设置根控制器,我就不写了太简单了。这是其他的代码直接复制即可。
ViewController.h
#import "ViewController.h"
#import "SecondViewController.h"
@interface ViewController ()
@property (nonatomic,strong) UILabel *nameLabel;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//设置跳转按钮
UIButton *btn=[[UIButton alloc]initWithFrame:CGRectMake(20, 180, 130, 50)];
[btn setTitle:@"跳转" forState:UIControlStateNormal];
[btn setTintColor:[UIColor orangeColor]];
btn.backgroundColor=[UIColor blackColor];
[btn addTarget:self action:@selector(clickAction) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
self.nameLabel=[[UILabel alloc] initWithFrame:CGRectMake(20, 80, 130, 50)];
self.nameLabel.backgroundColor=[UIColor orangeColor];
[self.view addSubview:self.nameLabel];
}
- (void)clickAction
{
SecondViewController *one=[[SecondViewController alloc]init];
[self presentViewController:one animated:YES completion:nil];
one.block=^(NSString *str){ self.nameLabel.text=str; };
}
SecondViewController.h
#import <UIKit/UIKit.h>
@interface SecondViewController : UIViewController
typedef void(^ablock)(NSString *);
@property (nonatomic,copy) ablock block;
@end
SecondViewController.m
#import "SecondViewController.h"
@interface SecondViewController ()
@property (strong, nonatomic) UITextField *nameTextField;
@property (nonatomic,strong) UIButton *btn;
@end
@implementation SecondViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//设置跳转按钮
UIButton *btn=[[UIButton alloc]initWithFrame:CGRectMake(20, 180, 130, 50)];
[btn setTitle:@"跳转" forState:UIControlStateNormal];
[btn setTintColor:[UIColor orangeColor]];
btn.backgroundColor=[UIColor blackColor];
[btn addTarget:self action:@selector(clickAction) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
self.btn=btn;
self.nameTextField=[[UITextField alloc] initWithFrame:CGRectMake(20, 80, 120, 40)];
self.nameTextField.layer.masksToBounds=YES;
self.nameTextField.layer.cornerRadius=20;
[self.view addSubview:self.nameTextField];
}
- (void)clickAction
{
if ([self notEmpty]) {
if (self.block) {
self.block(self.nameTextField.text);
//返回上一个界面。
[self dismissViewControllerAnimated:YES completion:nil];
}
}else{
[self showAlert];
}
}
-(BOOL)notEmpty{
if ([self.nameTextField.text length] == 0) {
return NO;
}
return YES;
}
-(void)showAlert{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:@"请输入名字" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Ok", nil];
[alert show];
}
@end
B 下面简单粗暴的来模拟一下回调使用的场景
1我现在在玩手机
2我手机突然没电了
3我让手机开始充电
4充电的时候我很无聊,要去看电视,等到手机充满电之后停止看电视,继续玩手机。
5我开始看电视
6手机充满电了,我听到手机响一下,我不看电视了,开始玩手机。
这个场景中哪里跟回调类似呢?哪里跟通知类似呢?其实我们可以认为手机充好电了通过回调的方式让我继续玩手机,也可以认为手机充好电了通知我可以继续玩手机,然后我主动继续玩手机。这里更像通知不像回调。但是换个思维想,如果手机本身没有回调机制,那他怎么能在恰好手机刚充满的时候响一下呢?
先不纠结这个问题,我们看看如果用block怎样来实现这样一个场景:
我们随便找个控制器写下以下代码:
- (void)viewDidLoad
{
[superviewDidLoad];
NSLog(@"我在玩手机");
NSLog(@"手机没电了");
[selfchargeMyIphone];
NSLog(@"我在看电视");
}
- (void)chargeMyIphone
{
//主线程休眠10秒
[NSThreadsleepForTimeInterval:10];
NSLog(@"电充好了");
}
注意 这里我用了NSTread sleep,这样会让我的主线程沉睡10秒钟,这个过程中我我真的可以一边看电视一边充电吗?
2015-09-10 15:54:37.719 38-demo-01[2046:113530]我在玩手机
2015-09-10 15:54:37.719 38-demo-01[2046:113530] 手机没电了
2015-09-10 15:54:47.720 38-demo-01[2046:113530] 电充好了
2015-09-10 15:54:47.721 38-demo-01[2046:113530] 我在看电视
所以我们应该让充电的线程和我看电视的线程错开执行!这里我们就不开新线程了,就让他10秒之后再执行吧。模拟下:
- (void)viewDidLoad {
[superviewDidLoad];
NSLog(@"我在玩手机");
NSLog(@"手机没电了");
[selfperformSelector:@selector(chargeMyIphone)withObject:nilafterDelay:10];
NSLog(@"我在看电视");
}
- (void)chargeMyIphone
{
NSLog(@"电充好了");
}
2015-09-10 15:53:06.723 38-demo-01[1985:112573] 我在玩手机
2015-09-10 15:53:06.724 38-demo-01[1985:112573] 手机没电了
2015-09-10 15:53:06.724 38-demo-01[1985:112573] 我在看电视
2015-09-10 15:53:16.724 38-demo-01[1985:112573] 电充好了
看起来没多大问题,但是我们还没写完我们的场景呢,我们想充好电之后继续玩手机?所以我们写在哪儿呢?
如果直接放在看电视后面:
NSLog(@"继续玩手机");
我们看看控制台:
2015-09-10 15:57:08.082 38-demo-01[2110:114748]我在玩手机
2015-09-10 15:57:08.083 38-demo-01[2110:114748]手机没电了
2015-09-10 15:57:08.083 38-demo-01[2110:114748]继续玩手机
2015-09-10 15:57:08.083 38-demo-01[2110:114748] 我在看电视
2015-09-10 15:57:18.083 38-demo-01[2110:114748] 电充好了
呵呵!电都没充好,你就直接继续玩了?所以这里应该是电充好以后我们再继续玩手机?
那么该怎么做?我们可以写进充电函数里吗?
- (void)chargeMyIphone
{
NSLog(@"电充好了");
NSLog(@"继续玩手机");
}
2015-09-10 15:59:34.257 38-demo-01[2199:116281]我在玩手机
2015-09-10 15:59:34.258 38-demo-01[2199:116281]手机没电了
2015-09-10 15:59:34.258 38-demo-01[2199:116281]我在看电视
2015-09-10 15:59:44.258 38-demo-01[2199:116281]电充好了
2015-09-10 15:59:44.258 38-demo-01[2199:116281]继续玩手机
看起来没多大问题,但是我们想想看,我们把 继续玩手机写在充电 函数的最后,
如果每次冲完电都继续玩手机,这个没什么问题,但是如果每次冲完电不总是玩手机
怎么办呢?如果逛街呢?这样的话,这样写就不对了,我们想让充电函数最后执行的那一行
是可变化的。有很多方式可以做到,但是这里最好的做法肯定是追加一个block替换掉我们写死的那句代码。
也就是在充电前已经安排好一个充电计划,今天充电完继续玩手机,明天改成逛街。
那么每次调用的还是那个充电函数,只是传入的参数不一样而已。
明白这一点就知道block的最终奥义了。改造下这个函数,为它加个包含代码的参数。
-(void)chargeMyIphone:(void(^)(void))finishBlock
{
NSLog(@"电充好了");
finishBlock();
}
这个追加的参数就是我们的block,
第一个void表示此block无返回值 ^ 为block type的标志第二个void表示这个block无参数,finishBlock就是它的名字。
无参数无返回值类型的匿名函数
就是我们最简单的block了,他非常方便我们回调。
因为没有返回值,没有参数,就相当于只有内部的可执行代码。
而我们讲一个固定的事件用一段代码作为参数传进来,并且一名字()形式来触发它,
那么这个函数的结尾就不会总是玩手机了,可以是任何事情!
- (void)viewDidLoad {
[superviewDidLoad];
NSLog(@"我在玩手机");
NSLog(@"手机没电了”);
[self chargeMyIphone:^{ NSLog(@"出去逛街 "); }];
NSLog(@"我在看电视");
}
-(void)chargeMyIphone:(void(^)(void))finishBlock
{
double delayInSeconds=10.0;
dispatch_time_t poptime=dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds *NSEC_PER_SEC));
dispatch_after(poptime,dispatch_get_main_queue(), ^(void){
NSLog(@"电充好了");
finishBlock();
});
}
2015-09-10 16:17:45.872 38-demo-01[2380:124074]我在玩手机
2015-09-10 16:17:45.873 38-demo-01[2380:124074]手机没电了
2015-09-10 16:17:45.873 38-demo-01[2380:124074]我在看电视
2015-09-10 16:17:56.868 38-demo-01[2380:124074]电充好了
2015-09-10 16:17:56.869 38-demo-01[2380:124074]出去逛街
现在代码的结果非常清晰
充电完成之后 我要去逛街
充电内不需要消耗10秒
充电的充实我可以看电视
充电完成之后 回头来触发我block中设置的出去逛街