规范、协议与接口
类是一种具体实现体,而协议则是定义了一种规范,定义了某一批类所需要遵守的规范。
协议不提供任何实现,它体现的是规范和实现分离的设计哲学。
让规范和实现分离正是协议的好处,是一种松耦合的设计。
tips:OC中协议的作用就相当于其他语言中接口的作用。
协议定义的是多个类共同的公共行为规范,协议里通常定义的是一组公用方法,但不会为这些方法提供实现,方法的实现则交给类去实现。
使用类别实现非正式协议
在上一篇博客中,我们提到过,使用类别实现非正式协议是类别的作用之一。
当某个类实现NSObject的该类别时,就需要实现该类别下所有方法,这种基于NSObject定义的类别即可认为是非正式协议。
下面是使用类别实现非正式协议的代码示例:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
//以NSObject为基础定义EaTable类别
@interface NSObject (EaTable)
- (void) taste;
@end
NS_ASSUME_NONNULL_END
上面我们给NSObject提供了一个Eatable类别,该类别可以看作一个非正式协议,相当于定义了一个规范,接下来所有继承NSObject类的子类都会自动继承Eatable中的taste:方法,我们可以根据我们的子类是否需要来决定是否要实现这个方法(一般都会实现)。
下来为NSObject(Eatable)派生一个子类。
接口部分:
#import <Foundation/Foundation.h>
#import "NSObject+EaTable.h"
NS_ASSUME_NONNULL_BEGIN
//定义类的接口部分
@interface FKApple : NSObject
@end
NS_ASSUME_NONNULL_END
这个子类虽然只是一个空类,但并不影响它的功能,因为它继承了NSObject(EaTable),只要在FKApple类的实现部分实现taste方法即可。
实现部分:
#import "FKApple.h"
//为FKApple提供实现部分
@implementation FKApple
- (void) taste {
NSLog(@"我讨厌吃苹果");
}
@end
在上面代码中可以看出来,我们在FKApple的实现部分对taste方法进行了实现,这就相当于FKApple类遵守了Eatable协议,然后就可以把FKApple类当作Eatable对象来调用。
主函数:
#import <Foundation/Foundation.h>
#import "FKApple.h"
int main(int argc, const char * argv[])
{
@autoreleasepool {
FKApple* apple = [[FKApple alloc] init];
[apple taste];
}
return 0;
}
运行结果:
正式协议的定义
定义正式协议的时候,不再使用@interface和@implementation关键字了,而是使用@protocol关键字,定义正式协议的语法如下:
@protocol 协议名 <父协议1, 父协议2>
{
零到多个方法定义......
}
对于上述语法,做出以下详细说明:
- 协议名应与类名采取相同的命名规则。
- 一个协议可以有多个直接父协议,但协议只能继承协议,不能继承类。
- 协议中定义的方法只有方法签名,没有方法实现。
- 协议中包含的方法可以是类方法,也可以是实例方法。
tips:因为协议定义的是多个类共同的公共行为规范,所以,协议里所有的方法都是公开的访问权限。
下面对定义正式协议的相关内容进行代码实现:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
//定义协议
@protocol FKOutput
//定义协议的方法
- (void)output;
- (void)addData(String msg);
@end
NS_ASSUME_NONNULL_END
上面定义了一个FKOutput协议,这个协议定义了两个方法:添加数据和输出数据。这就定义了FKOutput协议的规范:只要某个类能添加数据,并可以将数据输出,那它就是一个输出设备。
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
//定义协议
@protocol FKProoductable
//定义协议方法
- (NSData *)getProductTime;
@end
NS_ASSUME_NONNULL_END
上面定义了一个FKProductable协议,该协议代表了所有的产品都需要遵守的规范。其中定义了一个getProductTime方法,用来返回产品的生产时间。
#import <Foundation/Foundation.h>
#import "FKOutput.h"
#import "FKProductable.h"
NS_ASSUME_NONNULL_BEGIN
//定义协议,继承了FKutput、FKProductable两个协议
@protocol FKPrintable <FKOutput, FKProductable>
//定义协议的方法
- (NSString*) printColor;
@end
NS_ASSUME_NONNULL_END
上面定义了一个打印机协议,该协议同时继承以上两个协议。
tips:协议的继承和类的继承不一样,协议完全支持多继承,即一个协议可以有多个直接的父协议。和类继承相似,子协议继承某个父协议,将会获得父协议中的所有方法。一个协议继承多个父协议时,多个父协议排在<>中间,多个协议口见以(,)隔开。
遵守(实现)协议
在类定义的接口部分可以指定该类继承的父类,以及遵守的协议,一个类可以同时遵守多个协议,语法如下:
@interface 类名:父类<协议1, 协议2...>
如果程序需要使用协议来定义变量,有如下两种语法:
- NSObject<协议1,协议2...>* 变量;
- id<协议1,协议2...>* 变量;
这两个编译类型仅仅只是所遵循的协议类型,因此只能调用该协议中所定义的方法。
正式协议与非正式协议的差异
- 非正式协议通过为NSObject创建类别来实现,而正式协议直接使用@protocol创建;
- 遵守非正式协议通过继承带特定类别的NSObject来实现,而遵守正式协议则有专门的OC语法来实现;
- 遵守非正式协议不要求实现协议中定义的所有方法;而遵守正式协议则必须实现协议中定义的所有方法。
为了弥补遵守正式协议必须实现协议的所有方法造成灵活性不足,在OC还有两个关键字:
- @optional:位于该关键字只后、@required或@end之前声明的方法是可选的,实现类可选择是否实现这些方法。
- @required:位于该关键字之后、@optional或@end之前声明的方法是必需的,实现类必需实现这些方法。
通过在正式协议中使用以上两个关键字,正式协议完全可以代替非正式协议的功能。
协议与委托
协议体现的是一种规范,定义协议的类可以把协议定义的方法委托给实现协议的类,这样可以让类定义具有更好的通用性,因为具体的动作将由该协议的实现类去完成。无论是基于Mac的Cococa应用开发还是iOS开发,各种应用程序大量依赖委托这个概念。