分类与协议是OC比较有特色的部分。
从表面来看,
分类呢有点类似抽象方法在抽象类中(C++或者Java里的那个抽象类概念)。
协议类似接口(Java语言那个接口),但是又不能“一视同仁”。
分类概念
分类(Category) 允许向一个类文件中添加新的方法声明,
它不需要使用子类机制, 并且在类实现的文件中的同一个名字下定义这些方法。
其语法举例如下:
~~~
#import "ClassName.h"
@interface ClassName ( CategoryName )
// 方法声明
@end
~~~
#### 分类实例
上一篇多态性介绍中曾经使用过Vector和Scalar的例子,
下面我们为Vector增加“减”sub的方法。
Vector+sub.h文件
~~~
#import
#import "Vector.h"
@interface Vector (sub)
-(Vector *) sub: (Vector *) v;
@end
~~~
Vector+sub.m文件
~~~
#import "Vector+sub.h"
@implementation Vector (sub)
-(Vector *) sub: (Vector *) v {
Vector *result = [[Vector alloc] init];
[result setVec1: vec1 - [v vec1] andVec2: vec2 - [v vec2]];
return result;
}
@end
~~~
#### 调用的main函数
~~~
#import
#import "Vector+sub.h"
int main (int argc, const charchar * argv[]) {
Vector *vecA =[[Vector alloc] init];
Vector *vecB =[[Vector alloc] init];
id result;
//set the values
[vecA setVec1: 3.2 andVec2: 4.7];
[vecB setVec1: 32.2 andVec2: 47.7];
// print it
[vecA print];
NSLog(@" + ");
[vecB print];
NSLog(@" = ");
result = [vecA add: vecB];
[result print];
[vecA print];
NSLog(@" - ");
[vecB print];
NSLog(@" = ");
result = [vecA sub: vecB];
[result print];
// free memory
[vecA release];
[vecB release];
[result release];
return 0;
}
~~~
其中result = [vecA add: vecB] 中的add: 是Vector类原有的方法,
result = [vecA sub: vecB] 中的sub: 是Vector分类添加的方法。
分类是在Java和C++等面向对象的语言中没有的概念,
分类本质上是通过Objective-C的动态绑定而实现的。
通过分类使用能够达到比继承更好的效果。
从上面这个例子可以看到,分类提供了一种简单的方式,
用它可以将类的定义模块化到相关方法的组或分类中。
它还提供了扩展现有类定义的简便方式,并且不必访问类的源代码,也不需要创建子类。
~~~
#import "Fraction.h"
@interface Fraction (MathOps)
-(Fraction *) add: (Fraction *) f;
-(Fraction *) mul: (Fraction *) f;
-(Fraction *) sub: (Fraction *) f;
-(Fraction *) div: (Fraction *) f;
@end
~~~
注意,这既是接口部分的定义,也是现有接口部分的扩展。
因此,必须包括原接口部分,这样编译器就知道Fraction类。
按照惯例,作为分类的.h和.m文件的基本名称是由类的名称紧接着分类的名称。
例如:FractionMathOps.m;
一些程序员使用符号“+”来分隔类和分类的名称,比如Fraction+MathOps.h。不过不建议这样命名。
#### 类的扩展:
创建一个未命名的分类,且在括号“()”之间不指定名字,这是一种特殊的情况。
这种特殊的语法定义为类的扩展。
未命名类中声明的方法需要在主实现区域实现,而不是在分离的实现区域中实现。
未命名分类是非常有用的,因为它们的方法都是私有的。
如果需要写一个类,而且数据和方法仅供这个类本身使用,未命名类比较合适。
#### 关于分类的注意事项:
分类可以覆写该类中的另一个方法,但是通常认为这种做法是做虐的设计习惯。所以需要注意:
第一、覆写了一个方法后,再也不能访问原来的方法。(如果确实需要覆写方法,正确的选择可能是创建子类。)
第二、通过使用分类添加新的方法来扩展类不仅仅影响这个类,同时也会影响它的所有子类。
第三、对象/分类命名对必须是唯一的。因为大家使用的名称空间是程序代码、库、框架和插件共享的。
协议(Protocol )
与Java的Interface(接口 )或者C++的纯虚类相同,就是用来声明接口的。
协议只是定义了方法的列表,协议不负责实现方法,目的是让别的类来实现。
#### 以Graphics协议为例:
Graphics中定义了onDraw方法, 但是我们仔细分析一下onDraw方法不能实现的,
作为Graphics(几何图形)它无法知道它的子类如何绘制图形,
它只能规定绘制图名字为onDraw签名和返回值等信息, 但不能给出具体的实现,
因此Graphics(几何图形) 不应该设计成为类而应该设计成为协议。
~~~
@protocol Graphics
-(void) onDraw;
@end
~~~
协议只有接口部分, 没有实现部分, 所以没有m文件, 关键字@protocol ,
协议可以继承别的协议, 协议中不能定义成员变量。
协议实现类Ellipse
~~~
#import
#import "Graphics.h"
@interface Ellipse:NSObject {
}
@end
~~~
~~~
#import "Ellipse.h"
@implementation Ellipse
-(void)onDraw {
NSLog(@"绘制椭圆形");
}
@end
~~~
#### 协议实现类Triangle
~~~
#import
#import "Graphics.h"
@interface Triangle:NSObject {
}
@end
~~~
~~~
#import "Triangle.h"
@implementation Triangle
-(void)onDraw {
NSLog(@"绘制三角形");
}
@end
~~~
#### 代码说明:
协议的实现是在类声明的父类之后, 加上, 与类的单个继承不同,
协议可以实现多个, 表示要实现这个协议,
如果有多个协议要实现用“,” 号分隔: 。
#### 调用的main函数
~~~
#import
#import "Graphics.h"
#import "Ellipse.h"
#import "Triangle.h"
int main (int argc, const charchar * argv[]) {
id graphics;
graphics = [[Ellipse alloc] init];
[graphics onDraw];
[graphics release];
graphics = [[Triangle alloc] init];
[graphics onDraw];
[graphics release];
return 0;
}
~~~
从上面的例子可以看出:
协议是多个类共享的一个方法列表。
协议中列出的方法没有相应的实现,计划由其他人来实现。
协议提供了一种方式,用指定的名称定义一组多少有点相关的方法。
如果决定实现特定协议的所有方法,也就意味着要遵守(confirm to)或采用(adopt)这项协议。
定义一个协议很简单:只要使用@protocol指令,然后跟上你给出的协议名称。
例如:
~~~
@protocol NSCoping
- (id) copyWithZone: (NSZone *) zone;
@end
~~~
这里再提到两个重要的协议:
NSCopying
NSCoding
如果你的类采用NSCopying协议,则必须实现copyWithZone:的方法**,**
通过在@interface行的一对尖括号()内列出协议名称,可以告知编译器你正在采用的一个协议。
这项协议的名称放在类名和它的父类名称之后,
例如:
@interface AddressBook:NSObject
如果你的类采用多项协议,只需把它们都列在尖括号中,并用逗号分开,例如:
@interface AddressBook:NSObject
如果你定义了自己的协议,那么不必由自己实现它。
如果一个类遵守NSCoping协议,则它的子类也遵守NSCoping协议
(不过并不意味着对该子类而言,这些方法得到了正确的实现)。
@required和@optional关键字
@protocol MyProtocol
- (void) requiredMethod;
@optional
- (void) anOptionalMethod;
- (void) anotherOptionalMethod;
@required
- (void) anotherRequiredMethod;
@end
注意,这里使用了@optional指令。该指令之后列出的所有方法都是可选的。
@required指令之后的是需要的方法。
注意,协议不引用任何类,它是无类的(classless)。
任何类都可以遵循MyProtocol协议。
可以使用conformsToProtocol:方法检查一个对象是否遵循某些协议。
例如:
~~~
if ([currentObject conformsToProtocol: @protocol (MyProtocol)] == YES) {
...
}
~~~
这里使用的专用@protocol指令用于获取一个协议名称,并产生一个Protocol对象。
conformsToProtocol:方法期望这个对象作为它的参数。
通过在类型名称之后的尖括号中添加协议名称,可以借助编译器来检查变量的一致性。
例如:
id currentObject;
这告知编译器currentObject将包含遵守Drawing协议的对象。
如果这个变量保存的对象遵守多项协议,则可以列出多项协议 ,
如以下代码:
id myDocument;
定义一项协议时,可以扩展现有协议的定义。
如:
@protocol Drawing3D Drawing3D 协议也采用了Drawing协议。
最后要说的是,分类也是可以采用一项协议。
和类名一样,协议名必须是唯一的。
#### 代理:
协议也是一种两个类之间的接口定义。定义了协议的类可以看做是将协议定义的方法代理给了实现它们的类。
非正式(informal)协议:是一个分类,列出了一组方法但并没有实现它们。
因此,非正式分类通常是为根类定义的。有时,非正式协议也称为抽象(abstract)协议。
非正式协议实际上只是一个名称下的一组方法。
声明非正式协议的类自己并不实现这些方法,
并且选择实现这些方法中的子类需要在它的接口部分重新声明这些方法,
同时还要实现这些方法中的一个或多个。
注意:前面描述的@optional指令添加到了Objective-C 2.0语言中,用于取代非正式协议的使用。
#### 合成对象:
可以定义一个类包含其他类的一个或多个对象。
这个新类的对象就是所谓的合成(composite)对象,因为它是由其他对象组成的。
子类依赖于父类,改变了父类有可能会使得子类中的方法不能工作。