文章目录
- 协议
-
- [规范. 协议与接口](#规范. 协议与接口)
- 使用类别实现非正式协议
- 正式协议的定义
- [遵守 (实现) 协议](#遵守 (实现) 协议)
- 委托 (Delegate)
协议是Objective-C的一个重要知识点, 其作用类似于接口, 用于定义多个应该遵循的规范
协议
规范. 协议与接口
同一个类的内部状态数据、各种方法的实现细节完全相同,类是一种具体的实现体。而协议则定义了一种规范,协议定义某一批类所需要遵守的规范,它不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类中必须提供某些方法,提供这些方法的类就可满足实际需要。
协议不提供任何实现。协议体现的是规范和实现分离的设计哲学.
协议定义的是多个类共同的公共行为规范,这些行为是与外部交流的通道,这就意味着协议里通常是定义一组公用方法,但不会为这些方法提供实现,方法的实现则交给类去完成
使用类别实现非正式协议
类别(category)可以实现非正式协议, 这种类别以NSObject 为基础, 为NSObject 创建类别, 创建类别时即可指定该类别应该新增的方法
当某个类实现NSObject的该类别时, 就需要实现该类别下所有的方法, 这种基于NSObject定义的类别即可认为是非正式协议
objc
// 以NSObject为基础定义Eatable类别,作为非正式协议
@interface NSObject (Eatable)
- (void)taste;
@end
// 分类的实现(此处仅声明方法,无默认实现,由子类自行实现)
@implementation NSObject (Eatable)
// 非正式协议的方法无需在此实现,留给子类重写
@end
上面在 NSObject 的 Eatable 类别中定义了一个 taste 方法,接下来所有继承 NSObject 类的子类都会自动带有该方法,而且 NSObject 的子类可以根据需要,自行决定是否要实现该方法。当然,既然 Eatable 类别作为一个非正式协议使用,那么相当于定义了一个规范,因此,遵守该协议的子类通常都会实现这个方法。
objc
// 定义FKApple类,继承NSObject,遵循Eatable非正式协议
@interface FKApple : NSObject
@end
@implementation FKApple
// 实现Eatable非正式协议中的taste方法
- (void)taste {
NSLog(@"苹果营养丰富,口味很好!");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建FKApple对象并调用taste方法
FKApple *apple = [[FKApple alloc] init];
[apple taste];
// 演示:调用前检查方法是否实现
if ([apple respondsToSelector:@selector(taste)]) {
[apple taste];
} else {
NSLog(@"FKApple未实现taste方法");
}
}
return 0;
}
[apple respondsToSelector:@selector(taste)]
respondsToSelector:是NSObject协议的方法,作用是检查调用对象(这里是apple)是否实现了指定的选择器(方法)。@selector(taste)是将方法名taste转换成 Objective-C 中的选择器(SEL类型),用来标识这个方法。- 整行的返回值是
BOOL类型:YES表示apple实现了taste方法,NO则表示未实现。
虽然 Objective-C 编译器并不强制遵守非正式协议的类必须实现该协议中所有的方法,但如果该类没有实现非正式协议中的某个方法,那么程序运行时调用该方法,就会引发 unrecognized selector 错误。
正式协议的定义
和定义类不同, 正式协议不再使用@interface , @implementation 关键字, 而是使用@protocol 关键字, 定义正式协议的基本语法格式如下:
objective-c
@protocol 协议名<父协议1 : 父协议2> {
零到多个方法定义 ...
}
对上面语法格式的详细说明如下。
- 协议名应与类名采用相同的命名规则。即如果仅从语法角度来看,协议名只要是合法的标识符即可;如果要遵守 Objective-C 可读性规范,则协议名应由多个有意义的单词连接而成,每个单词首字母大写,单词与单词之间无须任何分隔福符。
- 一个协议可以有多个直接父协议,但协议只能继承协议,不能继承类
- 协议中定义的方法只有方法签名,没有方法实现;协议中包含的方法既可是类方法,也可是实例方法。
协议定义的是多个类共同的公共行为规范, 因此, 协议里所有的方法都具有公开的访问权限
定义一个协议
objc
@protocol FKOutput
// 定义协议的方法
- (void) output;
- (void) addData : (NSString*) msg;
@end
objc
@protocol FKProductable
- (NSDate*) getProduceTime;
@end
接下类定义一个打印机协议, 该协议同时继承上面的两个协议
objc
@protocol FKPrintable <FKOutput, FKProductable>
- (NSString*) printColor;
@end
协议的继承和类的继承不一样, 协议完全支持多继承, 即一个协议可以有多个直接的父协议. 和类继承相似, 自协议继承某个父类协议, 将会获得父协议里定义的所有方法.
一个协议继承多个父协议时, 多个父协议排在<>之间, 多个协议之间以英文逗号( , )隔开
遵守 (实现) 协议
在类定义的接口部分可指定该类继承的父类, 以及遵守的协议, 和语法格式如下;
objc
@interface 类名: 父类 <协议1, 协议2...>
一个类可以同时遵守多个协议
由于Objective-C协议的功能基本等同于接口, 因此, 有时候也把遵守协议说成实现协议, 实际上, Objective-C编译器也使用implement作为实现协议的说法.
objc
#import <Foundation/Foundation.h>
#define MAX_CACHE_LINE 10
@protocol FKOutput
// 定义协议的方法
- (void) output;
- (void) addData : (NSString*) msg;
@end
@protocol FKProductable
- (NSDate*) getProduceTime;
@end
@protocol FKPrintable <FKOutput, FKProductable>
- (NSString*) printColor;
@end
@interface FKPrinter : NSObject <FKPrintable> // 遵守FKPrintable 协议
@end
// 为 FKPrinter 提供实现部分
@implementation FKPrinter {
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 @"red";
}
@end
如果类实现定义了协议中的所有方法, 那么程序就可以调用该实现类所有实现的方法
objc
int main(int argc, char* argv[]) {
@autoreleasepool {
FKPrinter* printer = [[FKPrinter alloc] init];
// 调用FKPrinter 对象的方法
[printer addData: @"hello"];
[printer addData: @"hello"];
[printer output];
[printer addData:@"hello"];
[printer addData: @"hello2"];
[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];
}
return 0;
}
上面程序中创建了一个 FKPrinter 对象,该 FKPrinter 对象包含上面3个协议的方法,因此可以调用3个协议中的方法。
程序中的两行粗体字代码没有使用FKPrinter来定义变量,而是使用协议来定义变量,那么这些变量只能调用该协议中声明的方法,否则编译器会提示错误。如果程序需要使用协议来定义变量,则有如下两种语法。
-
NSObject<协议 1,协议 2...>*变量;
-
d<协议 1.协议 2...>变量;
通过上面的语法格式定义的变量,它们的编译时类型仅仅是所遵守的协议类型,因此只能调用该协议中定义的方法。
对比正式协议与非正式协议,不难发现存在如下差异。
- 非正式协议通过为NSObject创建类别来实现;而正式协议则直接使用@protocol创建。遵守非正式协议通过继承带特定类别的NSObiect来实现:而遵守正式协议则有专门的Objective-C 语法。
- 遵守非正式协议不要求实现协议中定义的所有方法:而遵守正式协议则必须实现协议中定义的所有方法。
为了弥补遵守正式协议必须实现协议的所有方法造成的灵活性不足,Objective-C2.0新增**@optional、@required** 两个关键字,其作用如下。
- @optional:位于该关键字之后、@optional 或@end 之前声明的方法是可选的--实现类既可选择实现这些方法,也可不实现这些方法。
- @required:位于该关键字之后、@required 或@end 之前声明的方法是必需的--实现类必须实现这些方法。如果没有实现这些方法,编译器就会提示警告。@required 是默认行为。
例如以下协议:
objc
#import<Foundation/Foundation.h>
@protocol FKOutput
@optional
- (void) output;
@required
- (void) addData: (NSString*) msg;
@end
上面协议中定义了两个方法, 该协议的实现类可选实现方法 output 方法, 但必须实现addData: 方法, 否则编译器就会提示警告
通过在正式协议中使用@optional, @required 关键字, 正式协议完全可以代替非正式协议的功能
委托 (Delegate)
委托 (Delegate) 是一种设计模式, 让一个对象把某些任务委托给另一个对象去处理
基本结构
委托方(Delegator):主动方。负责定义协议,并持有一个遵守该协议的代理对象。
协议(Protocol):约定书。规定了代理方需要实现的方法(哪些是必须的,哪些是可选的)。
代理方(Delegate):被动方。遵守协议并实现其中的方法,用来响应委托方的请求。
##委托实现
首先定义协议, (规定代理需要做哪些事情)
objc
OrderDelegate.h
#import<Foundation/Foundation.h>
@protocol OrderDelegate <NSObject>
- (void) didReceiveOrder: (NSString*) food;
@end
委托方 (持有代理的引用)
接口部分
objc
Boss.h
#import "OrderedDelegate.h"
@interface Boss: NSObject <OrderDelegate>
@property (nonatomic, weak) id<OrderDelegate> delegate;
- (void) placeOrder;
@end
注意: 这里是weak, 避免 Boss 和 Secretary 互相强引用导致内存泄漏
实现部分
objc
Boss.m
#import "Boss.h"
@implementation Boss
- (void) placeOrder {
if ([self.delegate respondsToSelector: @selectot(didReceiverOrder:)]) {
[self.delegate didReceiverOrder: @"hanbao"];
}
}
@end
respondsToSelector:是 Objective-C 中NSObject协议的核心方法,结合@selector(didReceiveOrder:)使用时,作用是检查代理对象是否实现了didReceiveOrder:这个协议方法,避免直接调用未实现的方法导致程序崩溃。
核心功能
接收一个SEL (方法选择器)参数,返回BOOL值:
-
YES:代理对象实现了该方法; -
NO:代理对象未实现该方法。 -
在调用代理方法前执行此检查,是 Objective-C 代理模式的必做安全校验。
代理方 (实现协方法)
接口部分
objc
Secretary.h
#import"OrderDelegate.h"
@interface Secretary: NSObject <OrderDelegate>
@end
实现部分
objc
Secretary.m
#import"Secretary.h"
@implementation Secretary
- (void) didReceiveOrder: (NSString*) foog {
NSLog(@"接到订单, 去订 %@", food);
}
@end
测试
objc
main.m
#import"Boss.h"
#import"Secretary.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
Boss* boss = [[Boss alloc] init];
Secretary* secretary = [[Secretary alloc] init];
boss.delegate = secretary; // 设置代理
[boss placeOrder]; // 触发代理方法
}
return 0;
}