首先回顾一下代理模式,它的基本说明如下图:
控制器先成为子控件的代理(delegate)并实现相应的代理方法,那么子控件在运作的过程中,遇到某些需要控制器进行配合的场景时,就可以通过delegate属性调用对应场景的代理方法,实现让控制器进行对应操作的效果。
块回调的基本模式如下图:
块回调方法的模型是这样的:在一个方法B内嵌套另一个方法C,当其他地方的方法A使用B这个方法的时候,B方法能够通过C来将一些信息传递回给A方法,A自己就可以拿到传回来的这些信息进行操作(C的调用在B内,具体实现在A内)。
下面以一个自定义的segmentControl为例来演示如何使用块回调来实现代理的效果,要求点击这个segmentControl上的按钮后,页面会有相应反应。它的效果如下图:
具体代码可在以下链接下载:
blockCallbackReplaceDelegate.zip
首先看看使用代理模式的实现方法:
(1)、首先定义segmentControl,这个自定义控件是根据初始化方法传进来的frame和按钮标题数组把整个segmentControl等分为对应个数的UIButton,这个最主要的初始化入口方法如下:
- (instancetype)initWithTitles:(NSArray *)titles frame:(CGRect)frame delegate:(id<SYSegmentControlDelegate>) theDelegate { if (self = [super initWithFrame:frame]) { ...//把传进来的参数赋给自身属性 self.delegate = theDelegate;//把传进来的控制器设为代理 [self configView];//初始化segmentControl的各部分布局 return self; } return nil; }
(2)、然后在segmentControl里的UIButton的点击监听方法如下:
-(void)buttonClick:(UIButton *)button{ self.selectedIndex = button.tag; //记录下点击的是哪个按钮 [self refreshButton]; //根据记录的内容刷新按钮的显示 //然后使用delegate调用代理方法,让控制器执行对应代码 if ([self.delegate respondsToSelector:@selector(segmentControl:didSelectedIndex:)]) { [self.delegate segmentControl:self didSelectedIndex:self.selectedIndex]; } }
(3)、在控制器中,viewDidLoad方法首先调用了usingDelegate方法来初始化segmentControl,usingDelegate方法的代码如下:
- (void)usingDelegate { NSArray *items = ... CGRect segmentControlFrame = ... SYSegmentControl *segmentControl = [[SYSegmentControl alloc]initWithTitles:items frame:segmentControlFrame delegate:self]; [self.view addSubview:segmentControl]; //refreshView方法会根据self.selectedIndex的内容决定显示什么内容 [self refreshView]; }
同时这种模式还需要代理方法的配合,控制器需要实现对应的代理方法,如下:
- (void)segmentControl:(SYSegmentControl *)segmentControl didSelectedIndex:(NSInteger)index { self.selectedIndex = index; [self refreshView]; }
在整个过程中,当segmentControl中的某个按钮被点中之后,按钮的点击监听方法会被调用,在这个方法中使用delegate(也就是控制器)去调用对应的代理方法,在控制器实现的代理方法中,根据segmentControl传过来的参数去刷新页面,显示对应内容。
然后看看使用块回调模式的实现方法:
(1)、块回调模式中,segmentControl的初始化入口方法和代理模式的有所区别,代码如下:
- (instancetype)initWithTitles:(NSArray *)titles frame:(CGRect)frame callback:(callbackBlock)block{ if (self = [super initWithFrame:frame]) { ...//把传进来的参数赋给自身属性 self.block = block; //把传进来的块赋给自身属性 [self configView]; return self; } return nil; }
(2)、segmentControl的按钮点击方法修改如下:
//两种模式共用一个点击触发方法,在方法内判断模式类型 -(void)buttonClick:(UIButton *)button{ ... if (...) { //省略代理模式的代码 ... }else if (self.block) { //在此处进行块回调 self.block(self.selectedIndex); } }
(3)、在控制器中,viewDidLoad方法首先调用了usingBlockCallback方法来初始化segmentControl,代码如下:
- (void)usingBlockCallback { NSArray *items = ... CGRect segmentControlFrame = ... __weak ViewController *weakself = self; SYSegmentControl *segmentControl = [[SYSegmentControl alloc]initWithTitles:items frame:segmentControlFrame callback:^(NSInteger index) { //块回调会回调到下面这两句代码 weakself.selectedIndex = index; [weakself refreshView]; }]; [self.view addSubview:segmentControl]; [self refreshView]; }
在这个过程中,控制器在初始化segmentControl的时候就将需要回调的代码也定义好了,segmentControl在需要控制器配合的时候,可以直接调用块实现回调控制器里的块代码的效果,控制器只需在块代码里定义好对应操作即可。
对比两种方法可以发现,使用块回调回比使用代理更加轻便,因为不需要去声明协议以及代理方法,也不用去实现代理方法,只需把要回调的内容定义在块参数里即可。
但是在使用场景比较复杂的情况下,块代码回调这种方式就不如代理模式了。比如说要实现在segmentControl刚开始点击、刚放开点击或者在某个特定按钮状态下点击另一个按钮等这些场景中让控制器做相应反应,那么块回调方式实现起来就不如代理模式了,需要定义多个块参数以对应多种回调情况,会让segmentControl的初始化方法显得臃肿。这时候使用代理模式可以比较清晰区分各个场景的逻辑。