以前写过一篇关于协议代理和委托的文章,后来经过几次整理不知道跑哪去了,最近还是有不少人问相关的问题,所以还是重新举个例子说明一下.
  在类的设计过程中经常会为类添加很多行为,也就是方法,但是有时候你会发现某些类应该实现的方法,放在另外一个类中去实现会比较合适,比较常见的是网络交互,很多人都会单独创建一个类来负责网络交互相关的行为.这种情况下使用委托代理的方式就很方便了(当然block也是一种很好的解决办法).

  举个例子来说明一下委托代理模式的用法,比如我现在创建了一个老板类Boss,老板拥有很多功能,比如发工资,签合同等等,同时老板也有很多杂事需要处理,比如打印文件,那么像老板这么高级别的领导去做这些杂事好像有点说不过去,所以他应该委托他的员工去做(不一定是员工,任何会用打印机打印文件的对象都可以,猫和狗也可以).我们先来创建一个老板类再一步一步实现这个过程

1
2
3
4
5
6
#import <Foundation/Foundation.h>
@interface Boss : NSObject
-(void)printDocument:(NSString *)fileName;
@end

  好了,老板类设计好了,根据设计,老板有个打印文件的需要,所以我们这里定义了一个printDocument:的方法(不一定要在这里定义需要调用代理的方法,根据需求可以在任意地方调用代理执行协议方法).

  那么之前我们说了,打印文件这种杂事不需要老板亲自动手,你要让会的人(任何东西)来做就行了,所以老板要先声明一下,需要一个对象,他必需会打印文档,这个声明在这里就叫做协议,好了,我们来给老板添加一个协议,在objc中协议的关键字是@protocol

1
2
3
4
5
6
7
8
9
10
11
12
#import <Foundation/Foundation.h>
@protocol bossProtocol <NSObject>
-(void)printFile:(NSString *)fileName;
@end
@interface Boss : NSObject
-(void)printDocument:(NSString *)fileName;
@end

  现在协议也声明好了,那么只需要再找一个能做这件事的对象就行了(遵守协议),所以我们要给老板类再添加一个能够遵守协议的对象,在这里这个对象就称为代理(老板的代理),前面说到这个代理不一定是个人,任何能够遵守这个协议的对象都可以,所以我们马上就会想到objc中id正是代表一个任意对象的意思,所以我们给老板添加一个id类型的对象做为代理,当然他必需遵守老板的协议,在objc中遵守协议的语法是用<>,中间写上遵守协议的名称,如果有多个用逗号隔开就行了,最后老板类声明部份的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
#import <Foundation/Foundation.h>
@protocol bossProtocol <NSObject>
-(void)printFile:(NSString *)fileName;
@end
@interface Boss : NSObject
-(void)printDocument:(NSString *)fileName;
@property (nonatomic,weak) id<bossProtocol> delegate;
@end

最后,老板需要打印文件了,之前说好了,他只要交给他的代理去做就行了,我们来看看老板类的实现

1
2
3
4
5
6
7
8
9
#import "Boss.h"
@implementation Boss
-(void)printDocument:(NSString *)fileName
{
[self.delegate printFile:fileName];
}
@end

  正常情况下,代理就很勤快的去打印了,但是由于软件开发是个很繁杂的工作,可能你有时忘记了给老板找个代理了,或者给老板找的代理根本就没有去做老板要求的工作,这会导致crash,所以安全的作法是在老板要打印文件之前先检查一下代理找好了没有,并且这个代理到底会不会打印文件,完整的代码如下:

1
2
3
4
5
6
7
8
9
10
11
#import "Boss.h"
@implementation Boss
-(void)printDocument:(NSString *)fileName
{
if (self.delegate && [self.delegate respondsToSelector:@selector(printFile:)]) {
[self.delegate printFile:fileName];
}
}
@end

  检验成果的时候到了,我们来实验一下这个老板到底能不能顺利的打印出文档,新建一个ViewController,并且添加一个按钮,点击的时候创建出一个老板,并且让老板打印一个”通知”文件,此时老板应该找一个代理来完成打印的工作,我们就直接让这个ViewController来完成吧.先上代码我们再来分析.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#import "ViewController.h"
#import "Boss.h"
@interface ViewController ()<bossProtocol>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *testBtn=[[UIButton alloc] init];
testBtn.backgroundColor=[UIColor purpleColor];
testBtn.frame=CGRectMake(100, 100, 100, 50);
[testBtn addTarget:self action:@selector(onClickButton:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:testBtn];
}
-(void)onClickButton:(id)sender
{
Boss *boss=[[Boss alloc] init];
boss.delegate=self;
[boss printDocument:@"通知"];
}
-(void)printFile:(NSString *)fileName
{
NSLog(@"printing file:%@",fileName);
}
@end

  为了让当前的ViewController能够成为老板的代理,首先他必需先说明一下,他会做老板要求的那些事,也就是要遵守协议,前面已经说过遵守协议的语法了,这里需要说明的一点是,遵守协议可以在.h文件的@interface尾部添加声明,也可以在.m文件的匿名分类处声明.
你光说你会做,但实际又没有做事也不行啊,所以ViewController既然已经夸下海口了,还是要老老实实的把事情给办了,所以在底部有个-(void)printFile:(NSString *)fileName方法的具体实现
最后我们让程序运行起来,点一下按钮
2016-07-25 23:01:29.820 testProtocol[12537:203185] printing file:通知
非常好,打印吭哧吭哧的在打印了
你们可以尝试一下新建一个其他的类来做老板的代理,考验一下掌握情况

  最后,我们来分析一下UITableView的代理模式,UITableView是ios开发中最重要的控件,没有之一,理解了UItableView基本上xcode中其他的类似的控件都是一样的模式.理解UITableView关键的一点在于弄清楚到底谁是谁的代理,当我们在VC中添加了tableView时,正常的思维会觉的画表格的应该是VC,而tableView是VC的代理.而UITabelView设计的巧妙之处就在于其实是反过来的.你可以这样理解,UITableView就是一个表格绘制工作者,当他要画表格的时候他需要知道表格的一些基本信息,有了这些信息之后他才知道表格要怎么画,你应该没听过哪个老板对员工说:”去给我画张表格”,然后什么都没说明的情况吧.TableView获取这些信息的方式就是通过代理.同样,任何知道表格绘制信息的对象都可以成为UITableView的代理,一般情况下都是表格所在VC做为表格的代理,除非你要对TableView做特殊的封装.
  为了让VC能够成为表格的代理,根据前面所说的,第一步我们要遵守表格的代理协议,查看一下文档发现表格有两个协议,一个是UITableViewDataSource,另外一个是UITableViewDelegate,其实模式是一样的,主要是完成的任务类型有所区别,前者主要负责界面的外观样式,后者主要负责和用户的交互行为.重声一下,要遵守表格的协议在声明的位置用声明一下就可以了.
  接着我们要告诉UITableView他的代理就是当前的VC,由于这些都是在VC内完成了,所以self就代表当前的VC

1
2
tableView.dataSource=self;
tableView.delegate=self;

接下来是表格开始制作的过程

  • 当TableView开始画表格时第一个他需要知道的信息是这个表格总共要画几行(之前还有一个section,比较少用,可以自己去理解,默认为1),他会通过协议方法
    -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    向代理进行讯问,所以你要在VC中实现这个方法,返会行数,一般情况下准备用于绘制表格的数据都会存放在数组中,所以直接返会array.count就行了
  • 接着他会问每一行的高度是多少,协议方法是
    -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath,你同样需要实现这个方法逐行返会高度,通过indexPath的row变量你可以知道行号,当然如果每一行高度都一样你直接返会一个固定值就可以了
  • 最后你要实现
    -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath这个协议方法,告诉tableView每一行是长什么样的,具体UITableViewCell怎么实现不在这次的说明范围内,网络上很多教程.

好了,前面的协议都实现了之后运行[tableView reload];应该就可以看到表格了.

原创文章,转载请注明出处,谢谢!