什么是协议
协议类似于接口,用于定义多个类应遵守的规范。它不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它规定这批类中必须提供某些方法。
协议不提供任何实现,让规范和具体的实现分离,使整个项目逻辑更加清晰。
协议定义的是多个类的公共行为规范,这些行为是与外部交流的通道,所以协议里通常是一组共用方法,但不提供实现,具体方法的实现将在类中完成。
使用类别实现非正式定义
在学习类别与扩展时,我们知道类别可以实现非正式定义,这种类别以NSObject为基础,为NSObject创建类别,创建类别的时候就可以指定该类别应该新增的方法。
当某一个类实现NSObject的该类别时,就需要实现类别下的所有方法,这样的类别即是非正式协议。
下面这段程序以NSObject为基础,定义了一个类别,其名称为Eatable。
@interface NSObject (Eatable)
- (void)taste;
@end
下面这段程序为NSObject派生了一个子类,并满足了taste方法,
这样Apple类相当于遵守了Eatable协议,接下来就可以把Apple当Eatable对象来调用。
@interface Apple : NSObject
@end
@implementation Apple
- (void)taste {
NSLog(@"苹果营养丰富");
}
@end
然后我们来测试一下:
#import <Foundation/Foundation.h>
@interface NSObject (Eatable)
- (void)taste;
@end
@interface Apple : NSObject
@end
@implementation Apple
- (void)taste {
NSLog(@"苹果营养丰富");
}
@end
int main() {
@autoreleasepool {
Apple *apple = [[Apple alloc] init];
[apple taste];
}
}
输出:
需要注意,对于实现非正式协议的类来说,OC并不强制实现该协议中的全部方法,那些未实现的方法显然不能调用,不然就会引发错误。
正式协议的定义
与定义类不同,正式协议不再使用@interface、@implementation等关键字,而是使用@protocol关键字。
正式定义协议语法:
@protocol 协议名<父协议1,父协议2...> {
//方法定义
}
需要注意:
- 协议名应与类名采用相同的“大驼峰”命名规则,即名称由多个单词连接而成,每个单词首字母都大写
- 一个协议可以有多个直接父协议,但协议只能继承协议,不能继承类
- 协议中定义的方法只有方法签名,没有方法实现;协议中包含的方法可以是类方法,也可以是实例方法;协议里的所有的方法都是公开的访问权限
我们先定义两个协议Output和Productable:
@protocol Output
- (void)output;
- (void)addData:(NSString *)msg;
@end
@protocol Productable
- (NSDate *)getProduceTime;
@end
然后定义一个Printable协议,继承上面的两个协议:
@protocol Printable <Output, Productable>
- (NSString *)printColor;
@end
协议不同于类继承中的“树”关系,一个协议可以有多个直接的父协议,多个父协议在< >中以 , 隔开。
遵守(实现)协议
在类定义的接口部分可以指定该类继承的父类,及遵守的协议。
@interface 类名 : 父类 <协议1, 协议2...>
下面的程序,我们为Printble协议提供一个实现类Printer
#define MAX_CACHE_LINE 10
@interface Printer : NSObject <Productable>
@end
@implementation Printer {
//使用数组缓存打印的数据
NSString *printData[MAX_CACHE_LINE];
//记录待打印的数据个数
int dataNum;
}
//打印所有数据
- (void)output {
while(dataNum > 0) {
NSLog(@"打印机使用%@打印:%@", self.printColor, printData[0]);
dataNum--;
for (int i = 0; i < dataNum; i++) {
printData[i] = printData[i + 1];
}
}
}
//增添数据
- (void)addData:(NSString *)msg {
if(dataNum >= MAX_CACHE_LINE) {
NSLog(@"输出队列已满");
}else {
printData[dataNum++] = msg;
}
}
//获取当前时间
- (NSDate *)getProduceTime {
return [[NSDate alloc] init];
}
//输出打印颜色
- (NSString *)printColor {
return @"红色";
}
@end
在主函数中测试一下
int main() {
@autoreleasepool {
Printer *printer = [[Printer alloc] init];
[printer addData:@"西邮"];
[printer addData:@"移动应用"];
[printer output];
[printer addData:@"开发"];
[printer addData:@"实验室"];
[printer output];
//创建一个Printer对象,当成Productable使用
NSObject<Productable> *p = [[Printer alloc] init];
//调用Productable协议中定义的方法
NSLog(@"%@", p.getProduceTime);
//创建一个Printer对象,当成Output使用
id<Output> out = [[Printer alloc] init];
//调用Output协议中定义的方法
[out addData:@"欢迎你"];
[out addData:@"!!!"];
[out output];
}
}
输出:
从程序中注释部分可以看出,我们可以用协议来定义变量,而且有两种方法:
- NSObject<协议1, 协议2…> *变量名;
- id<协议1, 协议2…> 变量名;
这样定义的变量,他们的编译时类型仅仅是遵守的协议类型,因此只能调用该协议中定义的方法。
对比正式协议与非正式协议,我们可以发现有许多差异:
- 非正式协议通过NSObject创建类别来实现;正式协议直接使用@protocol创建
- 遵守非正式协议通过继承带特定类别的NSObject来实现;遵守正式协议有专门的OC语法
- 遵守非正式协议通过不要求实现协议中定义的所有方法;遵守正式协议必须实现协议中的所有方法
在正式协议中,可以通过以下两个关键字控制方法是否是必要的:
- @aptional:位于该关键字后、@required或@end之前声明的方法是可选的
- @required:位于该关键字后、@optional或@end之前的声明方法是必需的
协议与委托
协议体现的是一种规范,定义协议的类可以把协议定义的方法委托给实现协议的类,这样可以让类定义具有更好的通用性质,而将具体实现部分放到实现类去完成。
举个例子,A类中有个方法,对两个数进行操作,将结果输出到控制台。但其中并没有说明如何操作者两个数,而是在输出前调用了协议中的方法,让遵守协议的类去实现这个方法。这样的好处是,在不修改A类的情况下,就可以自由修改操作类型。
创建AClass并创建协议,为AClass定义delegate属性和数字操作完输出的方法
//AClass.h
//创建协议,定义协议名和父类
@protocol AClassDelegate <NSObject>
//@requierd为必须实现的方法
@required
- (int)doSomethingWithNumber1:(int)num1 andNumber2:(int)num2;
@end
@interface AClass : NSObject
//delegate属性
@property (nonatomic,weak) id<AClassDelegate> delegate;
- (void)logAnswerWithNumber1:(int)num1 andNumber2:(int)num2;
@end
实现输出功能
//AClass.m
#import <Foundation/Foundation.h>
#import "AClass.h"
@implementation AClass
- (void)logAnswerWithNumber1:(int)num1 andNumber2:(int)num2 {
int answer = [self.delegate doSomethingWithNumber1:num1 andNumber2:num2];
NSLog(@"%d", answer);
}
@end
在BClass中遵守该协议
//BClass.h
#import <Foundation/Foundation.h>
#import "AClass.h"
//遵守协议
@interface BClass : NSObject <AClassDelegate>
@end
实现协议具体方法(如两数相加)
//BClass.m
#import <Foundation/Foundation.h>
#import "BClass.h"
@implementation BClass
- (int)doSomethingWithNumber1:(int)num1 andNumber2:(int)num2 {
return num1 + num2;
}
@end
控制台输出
//main.m
AClass *a = [[AClass alloc] init];
BClass *b = [[BClass alloc] init];
//b成为a的代理
a.delegate = b;
//调用a的方法
[a logAnswerWithNumber1:3 andNumber2:5];
输出:
之后对操作进行修改,只需在BClass中进行。这就达成了不改变AClass的情况下,对AClass的功能进行修改。