版权声明:原创作品,谢绝转载!否则将追究法律责任。
在现实生活中,当处理某一情况的时候人们往往遵循严格的程序。执法人员他们在打官司的收集证据和询问的时候一定要遵守协议。
在面向对象的语言中,最重要的是一个对象需要定义一些行为在某种情况下。例如:一个tableView希望能够和数据源交互目的是为了表明他要展示什么数据。这意味着数据必须回应tableView一组特殊的消息。
这个数据源可能是一些类的实例,例如一个Controller或者一个专门继承NSObject类的数据源类为了让tableView知道这个对象适不适合做这个数据源,所以必须声明的是让这个对象实现必须的方法。
Objective-c允许你定义协议,表明这个方法希望用在特殊的情况。这章的主要内容描述了定义一个标准协议的语法,并且声明一个类接口来遵守这个协议,这意味着这个类必须实现他需要实现的方法。
协议定义消息传递的规则:
一个类的接口声明了和这个类关联的属性和方法。协议和之前定义类接口相比,被用来声明方法和属性是为了一些特定的类。
声明协议的基本语法:
@protocol ProtocolName
// list of methods and properties
@end
协议可以包含类方法实例方法和属性的声明:
例如一个自定义View类用来展示饼状图如下图:
为了使这个View可能的重复利用,所有的决策信息应该留给另一个对象,一个数据源。这意味着多个实例相同的视图类,可能展示不同的信息通过和不同的数据源进行交互。
这个最低的信息需要包括这个饼图包含几个部分,每个部分的相对大小,并且每个部分的标题。因此这个饼图的协议方法大概包含以下几个不分:
@protocol XYZPieChartViewDataSource
- (NSUInteger)numberOfSegments;
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;
@end
注意:这个协议用无符号整形来定义标量值,后面章节会详细讨论。
这个饼图视图类将要需要一个属性来跟踪数据源对象。这个对象可以是任何类,因此这个属性的类型是id类型。唯一知道这个对象的信息是他符合相关的协议。
这个在View里面定义数据源的属性像这样:
@interface XYZPieChartView : UIView
@property (weak) id <XYZPieChartViewDataSource> dataSource;
...
@end
Objective-c用尖括号表示符合这个协议。这个例子声明了一个弱引用类型通用类型的对象指针并且符合XYZPieChartViewDataSource。
注意:代理和数据源的属性被声明弱引用类型的原因是避免强引用的循环。
通过给属性指定相关的协议,你将要得到一个警告如果你设置的这个对象的属性不符合这个协议,尽管这个属性的类型是通用类型的。不要担心这个实例对象是UIViewController还是NSObject。最重要的是他符合这个协议,意味着这个饼图知道他可以请求需要的数据。
协议可以有可选的方法:
默认情况下协议声明下的方法都是必须实现的方法。这意味着任何类遵守这个协议必须实现他的这些方法。
你也可以声明可选的方法在协议里面。这些方法是某些类选择实现的。
例如你可以决定这个显示饼图标题的这个协议方法是可选的。如果这个数据源对象不实现这个可选的方法。那么就没有标题显示在饼图上。我们可以如下声明可选的协议方法:
@protocol XYZPieChartViewDataSource
- (NSUInteger)numberOfSegments;
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;
@optional
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;
@end
在这个例子中只有后面这个方法是可选的。之前的那些方法没有说明因此默认还是必须实现的。
这个@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。
如果你试图调用了未定义在特殊协议里面的方法,那么你就会得到一个恶错误。为了避免这个编译错误你需要设置你的自定义协议采用NSObject协议。
协议是可以继承其他的协议的:
就像一个Objective-c类可以继承其他的父类一样,你可以指定一个协议符合另一个协议。
例如一个最好的实践定义你的协议符合NSObject协议(一些NSObject行为从其类接口中分离到一个单独的协议文件里)NSObject类是采用NSObject协议的。
通过表明你自己的协议符合NSObject的协议来表明任何对象采用自定义协议还将提供NSObject协议的方法。因为你可能用NSObject的一些子类,你不要为这些NSObject协议方法提供实现而担心。该协议提供是有用的就像上面描述的那样。
指定一个协议符合另一个协议,你需要在尖括号里提供另一个协议的名字就像这样:
@protocol MyProtocol <NSObject>
...
@end
在这个例子中任何采用自定义协议同样也能使用NSObject协议的方法。
符合协议:
声明一个类符合协议需要在尖括号里写上协议的名字像这样:
@interface MyClass : NSObject <MyProtocol>
...
@end
这意味着MyClass的实例不仅仅能响应自己在接口定义的方法,而且这个MyClass也提供在myProtocol必选的方法的实现。没有必要重新声明这些方法在接口文件里,采用协议是足够的。
如果你需要一个类采用多个协议,你可以指定他们作为一个以逗号分隔的列表像这样:
@interface MyClass : NSObject <MyProtocol, AnotherProtocol, YetAnotherProtocol>
...
@end
技巧:如果发现你自己符合了很多协议在类里面,这可能意味着你可能需要重构那些过去复杂的类,分裂了必须的行为在很多小类,每个都有清晰的责任。
一个相对新的OSX操作系统普遍存在的缺陷和IOS开发是使用一个应用程序委托类包含大多数应用程序的功能(管理底层数据结构,服务数据到多个用户界面元素,以及响应手势和其他用户交互)。随着复杂性的增加,类变的更难维护。
一旦你已经声明符合某个协议,这个类必须最少提供协议要必须实现方法的实现,以及任何你选的可选方法,这个编译器将要警告你如果你没有实现必须实现的方法。
注意:这个定义在协议里面的方法就像定义在接口里面的方法一样。这个方法的名字和参数类型在实现的时候必须和在协议里面定义的一样。
cocoa和cocoa touch定义了大量的协议
协议被cocoa和cocoatouch对象在很多不同的环境中使用。例如,tableView类即用数据源对象来提供他需要的信息。也定义了自己的数据源协议。tableView的类允许你设置委托对象,必须符合UITableViewDelegate协议,这个代理的责任负责处理用户交互或者定制显示的某些条目。
一些协议用于表示类之间的相似性。而不是与特定类的需求,一些协议相反与更普遍的cocoa或者cocoa touch沟通联系机制,可以采用多个不相关的类。
例如一些框架数据对象像集合对象NSArray和NSDictionay支持NSCoding协议,这意味着他可以编解码给他们的属性为了存档或者发布作为原始数据。NSCoding使把对象写入磁盘变的更容易,这些对象符合这个协议。
一些Objective-c的低级特性也依靠协议。为了使用快速枚举,例如,一个集合必须符合NSFastEnumeration协议,就像在Fast Enumeration Makes it East to Enumerate a Collection。此外,一些对象可以被copy,例如当用属性的copy关键字的时候,这些对象必须符合NSCopying协议,否则你将要在运行时得到一个异常。
协议都是匿名的
协议也可以在对象不确定的情况使用,或者需要隐藏。
例如一个框架的开发者可能选择不公布某些类的接口。因为这些类名字不知道,用这个框架的人不可能直接创建这个类的实例。相反的一些在框架的对象被典型的设计为返回一个创建好的实例对象。像这样:
id utility = [frameworkObject anonymousUtility];
为了使anonymousUtility对象更有用,这个框架开发者公布了协议的某些方法。尽管原始类的接口没有被提供。这意味着类保持匿名,对象仍然可以被使用在一个有限的方式:
id <XYZFrameworkUtility> utility = [frameworkObject anonymousUtility];
如果你写IOS的应用用core Data框架,例如你可能会遇到NSFetchedResultsController类。这个类帮助datasource对象为tableView提供存储数据,使他更容易提供行数的信息。
如果你能用到tableView的多个分区,你也可以得到一些分区信息的结果。而不是返回一个特定的类包含分区信息,NSFetchedResultsController返回一个匿名对象,这个匿名对象符合NSFetchedResultsSectionInfo协议。这意味着他仍然可以查询对象为你需要的信息,比如一个分区多少行:
NSInteger sectionNumber = ...
id <NSFetchedResultsSectionInfo> sectionInfo =
[self.fetchedResultsController.sections objectAtIndex:sectionNumber];
NSInteger numberOfRowsInSection = [sectionInfo numberOfObjects];
尽管你不知道sectionInfo 对象,NSFetchedResultsSectionInfo 协议表明他仍然可以响应numberOfObjects方法。