协议的作用:
类似于接口,用于定义多个类应该遵守的规范,但不会为这些方法提供实现,方法的实现则交给类去完成。
规范、类和实例的抽象关系
由图可看出,同一个类的内部状态数据、各种方法的实现细节完全相同,类是一种具体实现体。而协议则定义了了一种规范,协议定义某一批类所需要遵守的规范,它不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类中必须提供某些方法,提供这些方法的类就可满足实际需要。
协议不提供任何实现。协议体现的是规范和实现分离的设计哲学。让规范和实现分离也正是协议的好处,是一种松耦合的设计。
使用类别实现非正式协议
类别(catgory)可以实现非正式协议,这种类别以NSObject为基础,为NSObject创建类别,创建类别时即可指定该类别应该新增的方法。
当某个类实现NSObject的该类别时,就需要实现该类别下的所有方法,这种基于NSObject定义的类别即可认为是非正式协议。
例如:
以NSObject为基础,定义一个类别,类别名称为Eatable,代码如下
#import <Foundation/Foundation.h>
//以NSObject为基础定义Eatable类别
@interface NSObject (Eatable)
- (void) taste;
@end
上面为NSObject的Eatable类别中定义了一个taste:方法,接下来所有继承NSObject类的子类都会自动带有该方法,而且NSObject的子类可以根据需要,自行决定是否要实现该方法。
Eatable类别作为一个非正式协议使用,那么相当于定义了一个规范,因此,遵守该协议的子类通常都会实现这个方法。
再为NSObject(Eatable)派生一个子类:
#import <Foundation/Foundation.h>
#import "NSObject+Eatable.h"
//定义类的接口部分
@interface FKApple : NSObject
@end
#import "FKApple.h"
//为FKApple提供实现部分
@implementation FKApple
- (void) taste {
NSLog(@"苹果营养丰富,口味很好!");
}
@end
从上述的程序可以看到,虽然接口部分没有定义任何方法,但是这丝毫不会影响到该类的功能,因为它继承了NSObject(Eatable),只要在FKApple类的实现部分实现taste方法即可。
下面是FKApple类的测试代码:
#import <Foundation/Foundation.h>
#import "FKApple.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
FKApple* app = [[FKApple alloc] init];
[app taste];
}
return 0;
}
输出结果为:
Objective-C并不强制实现该协议中的所有方法,但是若不实现FKApple类中的taste方法,而且非正式协议本身也没有实现该方法,就会引发下面的错误:
正式协议的定义
正式协议的基本语法如下:
@protocol 协议名 <父协议1, 父协议2>
{
零个到多个方法定义...
}
上面语法的详细说明如下:
- 协议名与类名采用相同的规则命名。
- 一个协议可以有多个直接父协议,但协议只能继承协议,不能继承类。
- 协议中定义的方法只有方法签名,没有方法实现;协议中包含的方法既可是类方法,也可是实例方法。
协议定义的是多个类共同的公共行为规范,因此,协议里所有的方法都是公开访问权限。
下面定义一个协议:
#import <Foundation/Foundation.h>
//定义协议
@protocol Output
//定义协议的方法
- (void) output;
- (void) addData: (NSString*) msg;
@end
上面定义了一个Output协议,其中定义了两个方法,而协议并不关心方法的实现,就相当于定义了一个接口。
再定义一个Productable协议,该协议代表了所有的产品都需要遵守的规范:
#import <Foundation/Foundation.h>
//定义协议
@protocol Productable
//定义协议的方法
- (NSDate*) getProduceTime;
@end
接下来定义一个打印机协议,该协议同时继承上面两个协议:
#import <Foundation/Foundation.h>
#import "Output.h"
#import "Productable.h"
//定义协议,继承了上面两个协议
@protocol Printable <Output, Productable>
//定义协议的方法
- (NSString*) PrintColor;
@end
协议的继承和类继承不一样,协议完全支持多继承,即一个协议可以有多个直接的父协议。和类继承相似,子协议继承某个父协议,将会获得父协议里定义的所有方法。
一个协议继承多个父协议时,多个父协议排在<>中间,多个协议口之间以英文逗号( , )隔开。
遵守(实现)协议
在类定义的接口部分可指定该类继承的父类,以及遵守的协议,语法如下:
@interface 类名: 父类 <协议1, 协议2...>
从上面的语法格式可以看出,一个类可以同时遵守多个协议。
为Printable协议提供一个实现类:Printer,该实现类的接口部分代码如下:
#import <Foundation/Foundation.h>
#import "Printable.h"
//定义类的接口部分,继承NSObject,遵守Printable协议
@interface Printer : NSObject <Printable>
@end
Printer类实现部分代码如下:
#import "Printer.h"
#import "Printable.h"
#define MAX_CACHE_LINE 10
@implementation Printer
{
NSString* printData[MAX_CACHE_LINE];
int dataNum;
}
- (NSString*) PrintColor {
return @"红色";
}
- (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];
}
@end
Printer类实现了Printable协议,且也实现了Printable和Printable的两个父协议中的所有方法。假如实现类没有实现协议中的PrintColor方法,编译器会提示如下警告。
测试程序的代码如下:
#import <Foundation/Foundation.h>
#import "Printer.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
//创建Printer对象
Printer* printer = [[Printer alloc] init];
//调用Printer对象的方法
[printer addData:@"疯狂iOS讲义"];
[printer addData:@"疯狂XML讲义"];
[printer output];
[printer addData:@"疯狂Android讲义"];
[printer addData:@"疯狂Ajax讲义"];
[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];
}
return 0;
}
输出结果如下:
程序中的两行粗体字代码没有使用Printer来定义变量,而是使用协议来定义变量,那么这些变量只能调用该协议中声明的方法,否则编译器会提示错误。
如果程序需要使用协议来定义变量,有如下两种方法:
- NSObject<协议1, 协议2…>* 变量;
- id<协议1, 协议2…> 变量;
通过上面的语法格式定义的变量,它们的编译时类型仅仅只是所遵守的协议类型,因此只能调用该协议中定义的方法。
正式协议与非正式协议的差异:
- 非正式协议通过为NSObject创建类别来实现,而正式协议则直接使用@protocol创建。
- 遵守非正式协议通过继承带特定类别的NSObject来实现;而遵守正式协议则有专门的Objective-C语法。
- 遵守非正式协议不要求实现协议中定义的所有方法;而遵守正式协议则必须实现协议中定义的所有方法。
为了弥补遵守正式协议必须实现协议的所有方法造成灵活性不足,OC新增了@optional、@required两个关键字,其作用如下:
- @optional:位于该关键字之后、@required或@end之前声明的方法时可选的——实现类既可以选择实现这些方法,也可以不实现这些方法。
- @required:位于该关键字之后、@optional或@end之前声明的方法是必需的——实现类必须实现这些方法。如果没有实现这些方法,编译器就会提示警告。@required是默认行为。
例如定义如下协议:
@protocol Output
//定义协议的方法
@optional
- (void) output;
@required
- (void) addData: (NSString*) msg;
@end
上面协议中定义了两个方法,该协议的实现类可选实现output方法,但必须实现addData:方法,否则编译器就会提示警告。
协议与委托(delegate)
委托的定义:
就是某个对象指定另一个对象处理某些特定任务的设计模式。通俗来说,就是“某个对象”把要做的事情委托给“另一个对象”去做。
其中“某个对象”被称作委托者,“另一个对象”被称作是被委托者,即代理。
委托和代理的关系
如下图,委托方通过某种方式把任务分派出去给代理方处理,而两者之间的联系便是协议。
在程序中:一般情况下
委托需要做的工作有:
- 定义协议与方法
- 声明委托变量
- 设置代理
- 通过委托变量调用委托方法
代理需要做的工作有:
- 遵循协议
- 实现委托方法