协议(protocol)的作用类似于其他语言的接口,用于定义多个类应该遵守的规范。
协议定义了某些类所需遵守的规范,它不关心这些类的内部状态数据和实现细节,只规定这些类必须提供某些方法。提供你呢这些方法的类就可满足实际需要。
协议不提供任何实现
协议体现的是规范和实现分离的设计哲学。让规范与实现分离是一种松耦合设计,这正是协议的好处。
协议不提供任何实现,通常是定义一组公用方法。方法的实现交给类来完成。
使用类别实现非正式协议
为 NSObject 创建类别,创建类别时,就需要实现该类别下的所有方法。这种基于NSObject定义的类别即可认为是非正式协议。
示例程序:
FKApple.h
#import <Foundation/Foundation.h>
#import "NSObject+Eatable.h"
// 定义类的接口部分
@interface FKApple : NSObject
@end
FKApple.m
#import "FKApple.h"
// 为FKApple提供实现部分
@implementation FKApple
- (void) taste
{
NSLog(@"苹果营养丰富,口味很好!");
}
@end
NSObject +Eatable.h
#import <Foundation/Foundation.h>
// 以NSObject为基础定义Eatable类别
@interface NSObject (Eatable)
- (void) taste;
@end
EatableTest.m
#import <Foundation/Foundation.h>
#import "FKApple.h"
int main(int argc , char * argv[])
{
@autoreleasepool{
FKApple* app = [[FKApple alloc] init];
[app taste];
}
}
运行结果:2015-09-26 11:31:18.230 923[1709:72468] 苹果营养丰富,口味很好!
特别提示:
对于实现非正式协议的类而言,objective-c 并 不强制实现该协议中的所有方法,也就是说,上面的 FKApple 类也可以 不实现 taste 方法。
但是,如果 FKApple 类不实现 taste 方法,且非正式协议本身也没有实现该方法,运行该程序就会引起错误。
错误信息如下:
2015-09-26 12:23:47.369 923[2190:88420] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[FKApple taste]: unrecognized selector sent to instance 0x78e0a930'
注意:虽然 OC 编译器并不强制遵守非正式协议的类必须实现该协议中所有的方法,,但如果该类没有实现协议中的 某个方法,那么程序运行时如果调用该方法,就会引发 unrecognized selector 错误。
正式协议
定义正式协议
和定义类不同,正式协议使用@protocol 关键字。
定义正式协议的基本语法格式如下:
@protocol 协议名<父协议 1,父协议2>
{
0到多个方法定义....
}
语法说明:
1. 协议名应与类名采用相同的命名规则。——由多个有意义的单词连接而成,每个单词首字母大写,单词间无需任何分隔符。
2. 一个协议可以有多个直接父协议,,但协议只能继承协议,不能继承类。
3. 协议中 定义的方法中只有方法签名,没有方法实现。协议中包含的方法可以是类方法,也可以是实例方法。
4. 协议里所有的方法都是公开的访问权限——协议定义的是多个类共同单单公共行为规范。
5. 协议的继承与类的继承不一样,协议支持多继承——一个协议可以有多个直接的 父协议。与类的继承相似的是:子协议继承多个父协议,将会获得父协议里定义的所有方法。
6. 一个协议继承多个父协议时,多个父协议排在<>之间,彼此间以英文逗号隔开。
实现(遵守)协议
在类定义的接口部分可指定该类继承的父类,以及遵守的协议 。语法如下:
@interface 类名:父类<协议1,协议2...>
一个类可以同时遵守多个协议。
示例代码:
FKOutput.h
#import <Foundation/Foundation.h>
// 定义协议
@protocol FKOutput
// 定义协议的方法
@optional
- (void) output;
@required
- (void) addData: (NSString*) msg;
@end
FKProductable.h
#import <Foundation/Foundation.h>
// 定义协议
@protocol FKProductable
// 定义协议的方法
- (NSDate*) getProduceTime;
@end
FKPrintable.h
#import <Foundation/Foundation.h>
#import "FKOutput.h"
#import "FKProductable.h"
// 定义协议,继承了FKOutput、FKProductable两个协议
@protocol FKPrintable <FKOutput , FKProductable>
@required
// 定义协议的方法
- (NSString*) printColor;
@end
FKPrinter.h
#import <Foundation/Foundation.h>
#import "FKPrintable.h"
// 定义类的接口部分,继承NSObject,遵守FKPrintable协议
@interface FKPrinter : NSObject <FKPrintable>
@end
FKPrinter.m
#import "FKPrinter.h"
#define MAX_CACHE_LINE 10
// 为FKPrinter提供实现部分
@implementation FKPrinter
{
// 使用数组记录所有需要缓存的打印数据
NSString* printData[MAX_CACHE_LINE];
// 记录当前需打印的作业数
int dataNum;
}
- (void) output
{
//只要还有作业,继续打印
while(dataNum > 0)
{
NSLog(@"打印机使用%@打印:%@" , self.printColor , printData[0]);
// 将剩下的作业数减1
dataNum--;
// 把作业队列整体前移一位
for(int i = 0 ; i < dataNum ; i++)
{
printData[i] = printData[i + 1];
}
}
}
- (void) addData: (NSString*) msg
{
if (dataNum >= MAX_CACHE_LINE)
{
NSLog(@"输出队列已满,添加失败");
}
else
{
// 把打印数据添加到队列里,已保存数据的数量加1。
printData[dataNum++] = msg;
}
}
- (NSDate*) getProduceTime;
{
return [[NSDate alloc] init];
}
- (NSString*) printColor
{
return @"红色";
}
@end
如果实现类实现了协议 中所有的方法,这样程序就可以调用该实现类所实现的方法。测试代码如:
FKPrinterTest.m
#import <Foundation/Foundation.h>
#import "FKPrinter.h"
int main(int argc , char * argv[])
{
@autoreleasepool{
// 创建FKPrinter对象
FKPrinter* printer = [[FKPrinter alloc] init];
// 调用FKPrinter对象的方法
[printer addData:@"疯狂iOS讲义"];
[printer addData:@"疯狂XML讲义"];
[printer output];
[printer addData:@"疯狂Android讲义"];
[printer addData:@"疯狂Ajax讲义"];
[printer output];
// 创建一个FKPrinter对象,当成FKProductable使用
NSObject<FKProductable>* p = [[FKPrinter alloc] init];
// 调用FKProductable协议中定义的方法
NSLog(@"%@" , p.getProduceTime);
//创建一个FKPrinter对象,当成FKOutput使用 ①使用协议定义变量
id<FKOutput> out = [[FKPrinter alloc] init];
// 调用FKOutput协议中定义的方法 而②使用协议定义变量
[out addData:@"孙悟空"];
[out addData:@"猪八戒"];
[out output];
}
}
运行结果:
2015-09-26 13:52:03.357 923[2671:111493] 打印机使用红色打印:疯狂iOS讲义
2015-09-26 13:52:03.359 923[2671:111493] 打印机使用红色打印:疯狂XML讲义
2015-09-26 13:52:03.359 923[2671:111493] 打印机使用红色打印:疯狂Android讲义
2015-09-26 13:52:03.360 923[2671:111493] 打印机使用红色打印:疯狂Ajax讲义
2015-09-26 13:52:03.367 923[2671:111493] 2015-09-26 05:52:03 +0000
2015-09-26 13:52:03.368 923[2671:111493] 打印机使用红色打印:孙悟空
2015-09-26 13:52:03.368 923[2671:111493] 打印机使用红色打印:猪八戒
我们看到上面最后一段代码中变量 p 和 out 是使用协议来定义的变量。那么这些变量只能调用该协议中声明的方法,否则编译器 会提示错误。
用协议来定义变量的语法
那么用协议来定义变量的语法是怎样的呢?
用协议来定义变量的语法格式有2种:
1. NSObject <协议1,协议2..>* 变量;
2. id < 协议1,协议2...> 变量;
通过上面的语法格式定义的变量,它们的编译时类型仅仅只是所遵守的协议类型,因此只能调用该协议中定义的方法。
正式协议与非正式协议的区别
区别 | 非正式协议 | 正式协议 |
---|---|---|
1 | 通过为 NSObject创建类别来实现 | 直接使用@protocol 创建 |
2 | 遵守(实现)非正式协议通过继承带特定类别的 NSObject 来实现 | 遵守(实现)正式协议必须实现协议中定义的所有方法 |
3 | 遵守(实现)非正式协议不需实现协议中定义的所有方法 | 遵守(实现)正式协议必须实现定义中所有的方法 |
正式协议为提高灵活性增加的关键字
为了弥补遵守正式协议必须实现协议的所有方法造成的灵活性不足,OC增加了2个关键字:@optional,@required。
通过在正式协议中使用@optional,@required关键字,正式协议完全可以代替非正式协议的功能。
2个关键字作用如下:
- @optional:位于该关键字后,@required或@end之前声明的方法是可选的——实现类既可以选择实现这些方法,也可以不实现这些方法
- @required:位于该关键字后,@optional或@end之前声明的方法是必须实现的。如果没有实现这些方法,编译器会提示警告。@required是默认的行为。