iOS 分類(Categories)與類的拓展 ( Extensions)

沒有找到源出處。

轉自:http://www.cocoachina.com/bbs/read.php?tid=88131



Categories


catgory 允許你爲一個已經存在的類增加方法----甚至是一個你沒有source的類。
Categories是一種強大的特性,它允許你直接擴展類的功能,而不需要使用子類的方法來擴展。
使用categories,你可以把你自己的類的實現方法分佈在幾個不同的文件裏。
Class extensions與此相似,但是它允許在@ implementation代碼塊額外的增加自己需要的APIs,而不是在原始類的@interface代碼塊裏。

Adding Methods to Classes


你可以通過在一個帶category name的interface file中聲明它們,來爲一個類增加方法,你也是可以在帶同樣category name的implementation file裏
定義它們。
category name表明這些方法增加到一個在別處被聲明的類裏,而不是一個新類。
但是是要注意的是,你不能使用category來爲一個類增加額外的實例變量。

category 增加的這些方法的會成爲類類型的一部分。例如,編譯器會認爲這些通過category的方式增加到NSArray類裏面的方法就是NSArray實例中
的一部分。而那些通過繼承NSArray的方式,增加到NSArray子類裏的方法則不會被包含在NSArray類型裏。

Category methods可以做任何在類中正常定義的方法能做的事。在運行時,沒有任何區別。通過category 增加到類中的方法會被這個類的所有子類繼
承,就和此類的其它方法一樣。

Category的聲明看起來和一個interface的聲明非常相似(除了category的名字要列在類名後的括號裏和沒有指明超類之外)。除非它的方法不會訪問任
何類的實例變量,否則category必須import它擴展的類的文件裏來,如下:

#import  "ClassName.h "

@interface ClassName ( CategoryName )
//method declarations
@end

通常在實現文件裏要import自己的頭文件。一個通常的命名方式是category擴展的類名+category name。category的實現
(在ClassName+CategoryName.m文件裏)可能會像下面這樣:

#import  "ClassName+CategoryName.h"

@implementation ClassName ( CategoryName )
//method definitions
@end

需要注意的是category不可以爲要擴展的類聲明額外的實例變量;它只能包含方法。
然而,所有在類的作用域裏的實例變量也在category的作用域裏。前面的實例變量指的是類裏聲明過的實例變量,@private的也不例外。

爲一個類增加categories的數量是沒有限制的,但是每一個category 的名字必須要是不相同的,而且應該聲明和定義一個不同的方法集。

How You Can Use Categories

使用categories的方式有很多:

  • 擴展一個其它實施者定義的類
   例如,你可以爲Cocoa frameworks裏的類增加方法。增加的方法會被子類繼承而且在運行時也不會和原始的方法有任何不同。
  • 作爲子類的一個替代方式
  
 不需要定義一個子類來擴展已有的類,通過category你可以直接爲類添加方法。
    例如,你可以爲NSArray和其它的Cocoa classes添加categories.與添加子類的方式來比,你不需要你擴展的類的源代碼。

  • 把實現一個新類的方法分佈在多個源文件裏
   例如,你可以把一個很大的類的方法分組到幾個categories裏,然後把每個category放在自己的文件裏。當以這種方式使用時,categories在很
多方面對開發過程都是有幫助的:
     1.提供一個簡單的方式來組合相關的方法。被定義在不同的類裏的相似的方法可以被保存在同一個源文件裏。
           2.當一個類是由多個開發者共同定義的時候,可以簡化大類的管理。
   3.爲一個非常大的類的增量編譯提供方便。
   4.提高常用方法的本地參考。
   5.可以根據不同版本的程序配置不同的類,而無需爲不同版本保持相同的源代碼。

  • 可以用來聲明非正式協議
   例如:@interface NSObject ( MyXMLSupport )
- initFromXMLRepresentation: (NSXMLElement *)XMLElement;
- ( NSXMLElement *)XMLRepresentation;
      @end

雖然Objective-C語言目前允許使用category來通過重載繼承的類的方法或者甚至是類文件中的方法,但是這種做法是被強烈反對的。category不是子
類的替代品。使用category 來重載方法有很多重大的缺陷:
  • 當category 重載一個從父類繼承過來的方法,通常可以通過super關鍵字來調用父類的實現方法。然而,如果category重載一個擴展類本身存在的
  • 方法,就沒有喚醒原始實現方法的辦法了。
  • 同一個類的category不能聲明重載這個類的另一個category中聲明的方法。
   這一點非常的重要,因爲很多Cocoa類也是通過使用categories來實現的
   一個你試圖重載的框架中定義的方法可能本身就已經在一個category被實現了,如果你這樣做了,很可能使用得前面的category的方法的實現
失效。一些category methods的存在可能會導致整個框架的行爲發生變化。
   例如,如果你在NSObject的一個category中重載windowWillClose:委託方法,在你的程序裏所有窗口的委託將會使用category方法來回應;所
有NSWIndow實例的行爲都會改變。你爲一個框架類增加的Categories可能會導致行爲上很神祕的變化和程序的崩潰。

Categories of the Root Class


Category 可以爲任何的類添加方法,其中也包括root class。添加到NSObject類上的方法對於所有與你的代碼相關聯的類都是可用的。有時候爲
root class添加方法是非常有用的,但是它也是非常危險的。雖然從表面上看起來category所做出的修改可以被很好的理解,而且影響也是有限的,但
是繼承的機制使得它有了一個廣泛的作用域。你可能會對你程序裏不可見的類做出意想不到的修改;你可能會對你正在做的事會產生的結果一無所知。
甚者,當對你修改過什麼一無所知的人在你的程序上工作時,他們對於他們正在做的事也不會有一個充分的瞭解。

另外,當你爲root class實現方法時有兩點需要記住:
  • 發送消息給super是非法的(因爲NSObject沒有超類)類的對象可以執行root class中定義的實例方法

正常來說,類對象只能執行類方法。但是root class中定義的實例方法是一個特例。它們定義了一個類,在運行時系統中的所有對象都繼承這個類。類
對象是完全成熟的對象,它需要共享同一個類。
這個特性意味着你爲NSObject類在category定義的實例方法不僅要能被實例對象執行,而且也要能被類對象執行。例如:在方法體中,self可能代表
一個類對象,也可能是類的一個實例。



Extensions


除了它所聲明的方法必須要在相應類的主要@implementation代碼塊被實現以外,類的extensions就像一個匿名的categories。

一個類有一個公開聲明的API,同時有額外的方法聲明爲僅由類或框架類私有使用,這是很正常的。你可以在上面提到的一個私有的頭文件或實現文
件裏用一個category(或多於一個的category)來聲明這樣的方法。這樣是可行的,但是編譯器並不能確認所有被聲明的方法都被實現了。

例如,下面代碼裏的聲明和實現在編譯器裏並不會報錯,甚至setNumber: 方法沒有實現
也不會有錯:

@interface MyObject : NSObject
{
NSNumber * number;


- (NSNumber *)number;
@end

@interface MyObject ( Setter )
- (void)setNumber : (NSNumber *)newNumber;
@end

@implementation MyObject

- (NSNumber *)number
{
return number;
}
@end

然而,在運行時如果調用setNumber:方法,將會產生錯誤。

Class extensions允許你在本地爲一個類聲明額外需要的方法,而不需要在原始類的
@interface代碼塊去添加,正如下面的例子所示:

@interface MyObject : NSObject
{
NSNumber * number;
}

- (NSNumber * )number;
@end

@interface MyObject()
- (void)setNumber: (NSNumber *)newNumber;
@end

@implementation MyObject

- (NSNumber *)number
{
return number;
}

- (void)setNumber:(NSNumber *)newNumber
{
number = newNumber;
}
@end

上面的例子中,有幾點要注意的:

  • 在第二個@interface代碼塊的括號裏並沒有給出名字。

  • setNumber: 方法的實現出現在類的主@implementation代碼塊。

setNumber: 方法的實現必須得在類的主@implementation代碼塊裏(你不能在category
裏實現它)。如果不這樣,編譯器將會產生一個找不到setNumber:方法定義的警告。

看完上面的內容,應該有了一些初步的認識了,下面在具體說下應用方面例子。

Class extensions 被設計出來的目的是爲了解決二個問題。
第一就是便利編譯器能更好的驗證類的私有接口,第二個目的就是解決一個微秒而粗糙的properties(另一個objective-c 2.0的特性)問題.


有關更好的驗證類的私有接口:
當實現一個類,通常在類的@implementation塊會有一個方法集。它們作用於整個@implementation塊,在其它所有方法的之前實現,這樣當有其它
方法用到這些私有方法時 ,就不會有警告出現(如果它們被實現在最下面,那麼編譯器會發出警告)。但這樣的實現方式是很粗笨的,我們可以把所有
私有方法的聲明放在一個category裏面,然後把這個category放在.m實現文件頂部。就像下面這樣:

view plain
  

 @interface MyClass (SuperSecretInternalSauce)  

         - (void) doMyPrivateThing; 

         - (BOOL) canMyPrivateThingEatThis: (OtherClass *) aThing; 

  @end 

  @implementation MyClass  

...     

  @end



這些方法將不會在相應的@implementation MycClass (SuperSecretInternalSauce) 塊裏被實現,當然它們也不是一定要被實現的。
但這樣做的結果是編譯器將不會做確保你實現了所有在category裏聲明的方法的檢查,換句話說,編譯器也不會捕獲方法聲明中的拼寫錯誤。
這是因爲category如果沒有相應@implementation MycClass (SuperSecretInternalSauce)實現塊,那麼在objective-c裏它就是一個非正式協議。
它就是一個方法聲明集,裏面的方法可以有選擇的去實現,通常這種category會被聲明在這個類的了類裏。
由於 class extension 有效的擴展類的主接口,那麼把上面的一段聲明代碼改成下面這樣,也可以達到同樣的效果。

view plain

@interface MyClass ()  

- (void) doMyPrivateThing;  

- (BOOL) canMyPrivateThingEatThis: (OtherClass *) aThing;  

@end   

@implementation MyClass 

 ...  

@end  



這樣修改之後,如果類的@implementation塊裏沒有包含在extension中聲明的方法的實現,編譯器將會發出抱怨。


有關設計public readonly,private readwrite 的properties:
當我們設計屬性時,通常不會設計得太強大。爲實現這個目的,可以把它聲明在一個categories裏,通過特別的synthesis這個屬性,可以對它達到功
能性的限制或者完全禁止。
注意:synthesis在categories裏是被禁止的,因爲synthesis需要存儲,而在category裏不能有效的聲明一個存儲區,允許category合成訪問類實例變
量的方法是不可接受的,這樣太脆弱也太醜陋了。然而,爲了達到內部類和框架的目的,聲明一個對公共來說是隻讀,而對私有來說可以讀寫的
property也是可取的。一個額外的需求是synthesis 這樣的properties必須總是能原生而精確的synthesize setter和getter方法。特別是當聲明一個
atomic的property,開發者沒有辦法正確的手動編寫1/2的getter setter方法對;也沒法保證鎖定的資源不外露,這就是說,在這種情況下沒法保證原子
性。class extensions很優雅的解決了這個問題。
具體來說,你可以像下面這樣來聲明一個property:

view plain

@interface MyClass : NSObject  @property(readonly) NSView *targetView;  @end  

然後是實現文件:

view plain

@interface MyClass()  @property(readwrite) NSView *targetView;  @end   @implementation MyClass  @synthesize targetView;  @end  

這樣一個publicly readonly、privately readwrite 的property就完成了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章