协议的使用(Working with Protocols)

来源:互联网 发布:tcp端口被占用 编辑:程序博客网 时间:2024/05/05 21:27

在现实世界中,在处理某些情况时,人们往往需要遵守严格的程序。例如,执法官员在进行查询或收集证据时,要求“遵守协议”。
在面向对象编程的世界中,在给定的情况下,能够定义一组对象的行为是非常重要的。例如,为了弄清要显示什么,表视图能与数据源对象通信。这意味着数据源必须对表视图发送的消息进行响应。
数据源可以是任何类的实例,例如视图控制器(OS X 中NSViewController 的之类或iOS中UIViewController 的之类)或者一个继承自NSObject专用的数据源类。为了使表视图能够知道某个对象是否是合适的数据源,声明对象实现必要的方法。
Objective-C 允许声明协议,该协议声明的方法可用于特定情况。本章描述了定义一个正规协议的语法,并解释如何标记类接口为协议,这个类必须实现要求的方法。

协议定义消息合同

一个类接口定义了这个类的方法和属性。相比之下,协议是用来声明不依赖于任何特定类的属性和方法。
定义协议的基础语法如下:
@protocol ProtocolName// list of methods and properties@end
协议可以包括实例方法和类方法的声明,也包括属性。
举个例子,考虑定制一个视图类,用于显示饼图,如图所示
图5-1 定制的饼图
视图

为了尽可能重用视图,所有信息的决策都留个另一个对象,数据源。这意味着相同视图类的多个实例可以通过与不同的数据源交了显示不同的信息。
饼图视图需要的信息至少包括段的数量,每段的相对大小和每段的标题,因此,饼图的源数据协议如下:
@protocol XYZPieChartViewDataSource- (NSUInteger)numberOfSegments;- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;@end
注:该协议使用NSUInteger 值来标量无符号整数值。将在下一章讨论这种类型。
饼图视图类接口需要一个属性来跟踪数据源对象。这个对象可以是任何类,因此,这个基本属性类型为id。唯一知道的是,这个对象符合相关协议。
用来声明视图的数据源属性的语法如下:
@interface XYZPieChartView : UIView@property (weak) id <XYZPieChartViewDataSource> dataSource;...@end
Objective-C使用尖括号来表示协议。这个例子为一个通用对象指针声明一个弱属性,该对象符合XYZPieChartViewDataSource协议。
注:为避免强引用循环,委托和数据源属性通常标记为weak。
在属性上指定符合的协议,如果尝试将属性设置到不符合协议的对象上,会得到编译器警告,即使基本属性类类型是通用的。无所谓该对象是UIViewController 或者NSObject的实例。最重要的是,该对象符合协议,这表明饼图视图知道它可以请求所需的信息。

协议有可选方法

默认情况下,协议中声明的所有方法是必须实现的。这表明任何符合该协议的类必须实现这些方法。
也有可能在协议中指定可选方法。类可以在需要时实现这些方法。
举个例子,可能饼图的标题是可选的。如果数据源不实现titleForSegmentAtIndex:方法,那么视图上将不显示标题
可以使用指令@optional 标记方法为可选,如下:
@protocol XYZPieChartViewDataSource- (NSUInteger)numberOfSegments;- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;@optional- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;@end
在这种情况下,只有titleForSegmentAtIndex: 方法被标记为可选。前面的方法没有指令,所以是必须实现的。
@optional 指令适用于任何在它之后的直到协议声明的最后或者遇到另一个指令@required,如之间的方法。可以添加更多的方法到协议中:
@protocol XYZPieChartViewDataSource- (NSUInteger)numberOfSegments;- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;@optional- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;- (BOOL)shouldExplodeSegmentAtIndex:(NSUInteger)segmentIndex;@required- (UIColor *)colorForSegmentAtIndex:(NSUInteger)segmentIndex;@end
这个例子中的协议定义了三个必须实现的方法和两个可选方法。

在运行时检查可选方法是否实现

如果一个方法在协议中标记为可选,必须在调用该方法前检查是否实现该方法。
举个例子,饼图视图可以像下面一样测试段标题方法:
NSString *thisSegmentTitle;    if ([self.dataSource respondsToSelector:@selector(titleForSegmentAtIndex:)]) {        thisSegmentTitle = [self.dataSource titleForSegmentAtIndex:index];    }
respondsToSelector: 方法使用一个选择器,这个选择器是用来指向编译后的方法。可以通过@selector() 指令指定方法名称来提供正确的标识符。
如果在这个例子中的数据源实现了这个方法,则使用标题,否则,标题为nil。
记住:本地对象变量自动初始化为nil。
如果视图调用上边定义的符合协议的id 的respondsToSelector: 方法,因为它没有实例,会编译错误。一旦使用带协议的id,则检查所有静态类型,如果试图调用指定协议中未定义的方法,则会报错。避免编译错误的一种方法是设置自定义协议采用NSObject 协议。

协议继承

Objective-C 类可以继承父类,协议也一样。
举个例子,可以定义你的协议符合NSObject协议(一些NSObject行为从类接口划分到单独的协议中,这些NSObject类采用NSObject协议)。
如果自定义协议符合NSObject 协议,那么采用自定义协议的对象也将为NSObject 协议方法提供实现。因为可能使用NSObject的一些之类,不用考虑为这些NSObject 方法提供自定义实现。协议采用是有效的,但只针对上边描述的情形。
指定一个协议符合另一个协议,可以在尖括号中填写其他协议的名称,如下:
@protocol MyProtocol <NSObject>...@end
在这个例子中,任何采用MyProtocol 的对象将有效的采用NSObject 协议中声明的所有方法

遵守协议

使用尖括号声明一个类采用某个协议,如下:
@interface MyClass : NSObject <MyProtocol>...@end
这表明MyClass 的任何实例不仅会响应接口中声明的方法,而且为MyProtocol中必须实现的方法提供了实现。不需要在类接口重新定义协议方法,采用协议就可以了。
注:编译器不会自动生成协议中声明的属性。
如果一个类须遵守多个协议,可以用逗号分隔列表,如下:
@interface MyClass : NSObject <MyProtocol, AnotherProtocol, YetAnotherProtocol>...@end
提示:如果发现在一个类中采用了大量的协议,这是个信号表明需要重构这个过于复杂的类,可以通过必要的方法将其拆分到多个更小的类中,每个类都有明确的责任。
对于新OS X 和iOS 开发者有一个相对常见的现金:使用一个应用程序代理类来包含应用程序的大部分功能(管理底层数据结构,提供数据到多个用户界面原始,以及相应手势和其他用户交互)。随着复杂性的增加,这个类会变得更加难以维护。
一旦表明要遵守协议,类必须实现那些必须实现协议的方法,以及你选择的可选方法。如果没实现任何一个必须实现的方法,编译器将会出现警告。
注:在协议中声明的方法就和其他声明一样。实现中的方法名称和参数类型必须与协议中声明的一样。

Cocoa和Cocoa Touch定义了大量的协议

在各种不同的情况下,Cocoa 和Cocoa Touch对象使用协议。例如,表视图类(OS X为NSTableView 而iOS为UITableView )都使用一个数据源对象来为他们提供必要的信息。他们都各自定义了自己的数据源协议,如图上文XYZPieChartViewDataSource 协议一样。这个两个表视图类允许设置委托对象,这个对象必须符合NSTableViewDelegate 或UITableViewDelegate 的相关协议。这个委托负责处理用户交互或定制显示某些项。
一些协议是用于表示类之间没有相似之处。一些协议与通用Cocoa 或Cocoa Touch 采用多个不相关类的通信机制相关,而不是用来链接指定类需求。
例如,许多框架模型对象(如集合类中的NSArray 和NSArray )支持NSCoding 协议,这表明他们可以为编码和解码属性或分布作为原始数据。将整个对象图写入到磁盘中,提供给每个采用协议的对象,NSCoding 将这些变得相对容易。
一些Objective-C语言级功能也依靠协议。例如,为了使用快速枚举,必须采用NSFastEnumeration协议,在中有描述。此外,一些对象可以被复制。此外,一些对象可以被复制,例如当使用 Copy Properties Maintain Their Own Copies中描述的协议属性。你试图复制的对象必须符合NSCopying 协议,否则会运行异常。

协议可用于匿名类

当不知道对象的类或者必须隐藏时,协议也很有用。

举个例子,一个框架开发人员可能会选择不再框架内发布接口。因为类名不知道,框架用户不可能直接创建这个类的实例。相反,框架中的其他对象通常会指定返回一个现成的实例,如下:

id utility = [frameworkObject anonymousUtility];

为了让anonymousUtility 对象有用,框架开发者可以发布一个协议显示一些方法。即使不提供原始类接口,即这个类是匿名的,用有限的方式,对象仍然可以使用。
id <XYZFrameworkUtility> utility = [frameworkObject anonymousUtility];
如果在写iOS应用,这个引用使用核心数据框架。例如,可能会遇到NSFetchedResultsController 类。这个类是用来帮助一个数据源对象提供存储数据到一个iOS UITableView,并便于提供信息如行数。
如果正在使用的表视图内容分成多个部分,可以访问一个控制器来获取相关信息。而不是返回一个包含这些信息的特定类,NSFetchedResultsController 类返回一个匿名对象,这个对象符合NSFetchedResultsSectionInfo 协议。可以查询所需对象的信息,例如一个section中的行数。
NSInteger sectionNumber = ...    id <NSFetchedResultsSectionInfo> sectionInfo =            [self.fetchedResultsController.sections objectAtIndex:sectionNumber];    NSInteger numberOfRowsInSection = [sectionInfo numberOfObjects];
即使不知道sectionInfo 对象的类,NSFetchedResultsSectionInfo 协议规定它可以响应numberOfObjects 的消息


官方原文:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithProtocols/WorkingwithProtocols.html

0 0
原创粉丝点击