协议
协议是OC的一个重要知识点,其作用类似于接口,用于定义多个类应该遵守的规范。
规范、协议与接口
Objective-C中协议的作用就相当于其他语言中接口的作用协议定义的是多个类共同的公共行为规范,这些行为是与外部交流的通道,这就意外着协议里通常是定义一组公用方法,但不会为这些方法提供实现,方法的实现则交给类去完成。
使用类别实现非正式协议
通过前面的介绍可知,类别(catgory)可以实现非正式协议,这种类别以NSObject为基础,为NSObject创建类别,创建类别时即可指定该类别应该新增的方法。
当某个类实现NSObject的该类别时,就需要实现该类别下的所有方法,这种基于NSObject定义的类别即可认为是非正式协议。
事例
我们以NSObject为基础,定义一个类别,类别名称为Eatable,该类别的代码如下:
先定义一个以NSObject为基础的类别,类别名称为Eatable,该类别的代码如下:
#import <Foundation/Foundation.h>
//以NSObject为基础定义Eatable类别
@interface NSObject (Eatable)
-(void) taste;
@end
注意我们定义的这个类别存放于一个名为NSObject+Eatable.h的*.h文件中。
上面为NSObject的Eatable类别中定义了一个taste:方法,接下来所有继承NSObject类的子类都会自动带有该方法,而且NSObject的子类可以根据需要,自行决定是否要实现该方法。当然,既然Eatable类别作为一个非正式协议使用,那么相当于定义了一个规范,因此,遵守该协议的子类通常都会实现这个方法。
接下来为NSObject(Eatable)派生一个子类。
代码如下:
#import <Foundation/Foundation.h>
#import "NSObject+Eatable.h"
@interface FKApple : NSObject
@end
我们可以注意到该FKApple子类只是一个空类,该类中并为定义任何方法,但这丝毫不会影响FKApple类的功能,因为它继承了NSObject(Eatable),只要在FKApple类的实现部分实现taste方法即可。
下面是FKApple类的实现部分代码:
#import "FKApple.h"
@implementation FKApple
-(void) taste
{
NSLog(@"苹果营养丰富,口味很好!");
}
@end
从上面的代码可以看出,FKApple类实现了taste方法,这样FKApple类就相当于遵守了Eatable协议,接下来就可以把FKApple类当成Eatable的对象来调用。
下面是FKApple类的测试代码:
#import <Foundation/Foundation.h>
#import "FKApple.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
FKApple* app = [[FKApple alloc] init];
[app taste];
}
}
我们可以看到运行结果如图所示:
可以看到taste成功的被调用。
正式协议的定义
和定义类不同,正式协议不再使用@interface、@implementation关键字,而是使用@protocol关键字。定义正式协议的基本语法如下:
@protocol 协议名 <父协议1,父协议2>
{
零个到多个方法定义...
}
上面语法的详细说明如下:
1.协议名应与类名采用相同的命名规则。即如果仅从语法角度来看,协议名只要是合法的标识符即可;如果要遵守OBjective-C可读性规范,则协议名应由多个有意义的单词连接而成,每个单词首字母大写,单词与单词之间无须任何分隔符。
2.一个协议可以有多个直接父协议,但协议只能继承协议,不能继承类。
3.协议中定义的方法只有方法签名,没有方法实现;协议中包含的方法即可是类方法,也可以是实例方法。
协议定义的是多个类共同的公共行为规范,因此,协议里所有的方法都是公开的访问权限。
事例
我们来定义一个协议,代码如下:
#import <Foundation/Foundation.h>
//定义协议
@protocol FKOutput <NSObject>
//定义协议的方法
-(void) output;
-(void)addData:(NSString*) msg;
@end
上面定义了一个FKOutput协议,这个协议定义了两个方法:表示添加数据的addData()和表示输出的output()方法。这就定义了FKOutput协议的规范: 只要某个类能添加数据,并可以将数据输出,那它就是一个输出设备,至于这个设备的实现细节,该协议不关心。
接下来再定义一个FKProductable协议,该协议代表了所有的产品都需要遵守的规范。
#import <Foundation/Foundation.h>
//定义协议
@protocol FKProductable
//定义协议的方法
-(NSDate*)getProduceTime;
@end
我们可以看到这个协议定义了一个getProduceTime方法,该方法返回产品的生产时间。这表明,无论什么产品都应该提供一个getProduceTime方法来换取产品的生产时间。
接下来我们定义一个打印机协议,该协议同时继承上面的两个协议,代码如下:
#import <Foundation/Foundation.h>
#import"FKOutput.h"
#import "FKProductable.h"
//定义协议,继承了FKOutput、FKProductable
@protocol FKPrintable <FKOutput , FKProductable>
//定义协议的方法
-(NSString*)printColor;
@end
协议的继承和类继承不一样,协议完全支持多继承,即一个协议可以有多个直接的父协议。和类继承相似,子协议几层呢某个父协议,将会获得父协议里定义的所有方法。
一个协议继承多个父协议时,多个父协议排在<>中间,多个协议口之间以英文逗号(,)隔开。
遵守(实现)协议
在类定义的接口部分可指定该类继承的父类,以及遵守的协议,语法如下:
@interface 类名 : 父类<协议1,协议2...>
从上面的语法格式可以看出,一个类可以同时遵守多个协议。
事例
下面为FKPrintable协议提供一个实现类: FKPrinter,该实现类的接口部分代码如下:
#import <Foundation/Foundation.h>
#import "FKPrintable.h"
//定义类的接口部分,继承NSObject,遵守FKPrintable协议
@interface FKPrinter : NSObject<FKPrintable>
@end
接下来为FKPrinter类提供实现部分,该实现部分代码如下:
#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
上面的FKPrinter类实现了FKPrintable协议,且也实现了FKPrintable和FKPrintable的两个父协议中的所有方法。假如实现类未实现协议中的printColor方法,编译器会有警告。
如果实现类实现了协议中的所有方法,这样程序就可以调用该实现类所实现的方法。测试代码如下:
#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];
}
}
上面的程序中创建了一个FKPrinter对象,该FKPrinter对象遵守上面3个协议的方法,因此可以调用3个协议中的方法。
程序中我们可以看到有两行代码没有使用FKPrinter来定义变量,而是使用协议来定义变量,那么这些变量只能调用该协议中声明的方法, 否则编译器会提示错误。
如果程序需要使用协议来定义变量,有如下两种方法:
NSObject<协议1,协议2…>* 变量;
id<协议1,协议2…>*变量;
通过上面的语法格式定义的变量,它们的编译时类型仅仅只是所遵守的协议类型,因此只能调用该协议中定义的方法。
正式协议与非正式协议的差异
1.非正式协议通过为NSObject创建类别来实现,而正式协议则直接使用@protocol创建。
2.遵守非正式协议通过继承带特定类别的NSObject来实现;而遵守正式协议则有专门的Objective-C语法。
3.遵守非正式协议不要求实现协议的所有方法;而遵守正式协议则必须实现协议中定义的所有方法。
为了弥补遵守正式协议必须实现协议的所有方法造成灵活性不足,Objective-C2.0新增了@optional、@required两个关键字,其作用如下:
1.@optional:位于该关键字之后、@required或@end之前声明的方法是可选的——实现类即可选择实现这些方法,也可不实现这些方法。
==2.@required:位于该关键字之后、@optional或@end之前声明的方法是必需的——实现类必须实现这些方法。如果没有实现这些方法,编译器就会提示警告。@required是默认行为。
例如,如下协议:
@protocol FKOutput
//定义协议的方法
@optional
(void) output;
@required
(void)addData(String msg);
@end
上面协议中定义了两个方法,该协议的实现类可选实现output方法,但必须实现addData:方法,否则编译器就会提示警告。
通过在正式协议中使用@optional、@required关键字,正式协议完全可以代替非正式协议的功能。