分類與協議是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)對象,因為它是由其他對象組成的。
子類依賴于父類,改變了父類有可能會使得子類中的方法不能工作。