iphone 開發Categories 、Extensions 區別


Category和Extension這兩個概念,即便對於Java程序員,可能也會感到陌生。這是Objective C為程序員提供的兩個強大的動態機制——簡單地說,它們允許程序員為已有的對象添加新的方法,即便是在沒有該對象的源代碼的情況下。

Category准確的定義是這樣的:Category擁有一個名字,它不需要使用子類(Subclassing)機制,就允許向一個類文件中添加新的方法聲明,並且在類實現的文件中的同一個名字下定義這些方法。其語法舉例如下:

 

#import "ClassName.h"
 
@interface ClassName ( CategoryName ) 
// method declarations 
@end

 

不過到現在為止,Category這個名字看起來仍然讓人摸不着頭腦——Category的中文是分類和范疇的意思——即便這個動態機制很強大,跟分類有什么關系呢?

這是因為利用這個機制,程序員可以把一堆方法分門別類,分成若干組,每組方法用一個Category名字加以命名,定義在同一個文件里。這個就是為什么把這個機制叫做Category的原因。

注意Category只能用於方法,不能用於成員變量。

 

理解了Category,Extension就不難理解了。Extension是Category的一個特例,其名字為匿名(為空),並且新添加的方法一定要予以實現。(Category沒有這個限制)

 

下面這里是翻譯的objective-c-primer.pdf 中的相關資料,可以對這兩個概念有一個初步了解。


Categories and Extensions

  

Categories

 

catgory 允許你為一個已經存在的類增加方法----甚至是一個你沒有source的類。

Categories是一種強大的特性,它允許你直接擴展類的功能,而不需要使用子類的方法來擴展。

使用categories,你可以把你自己的類的實現方法分布在幾個不同的文件里。

Class extensions與此相似,但是它允許在implementation代碼塊額外的增加自己需要的APIs,而不是在原始類的@interface代碼塊里,  extension中聲明的方法是私有的,只有主implement能調用,外部的類無法調用。

 

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)來聲明這樣的方法:    

  {     private方法

        Objective-C中的private方法是通過category實現的,實現文件中我們聲明一個類的category,在這里面的方法就是  private方法。類的對象是不可以進行調用的,同樣由於該方法的聲名是在類的實現文件中,所以子類也是不能重寫該方法的。

這樣是可行的,但是編譯器並不能確認所有被聲明的方法都被實現了。 

 http://3426724.blog.51cto.com/3416724/696723  

 }

例如,下面代碼里的聲明和實現在編譯器里並不會報錯,甚至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實現文件頂部。

就像下面這樣:

 

 
  1. @interface MyClass (SuperSecretInternalSauce)  
  2. - (void) doMyPrivateThing;  
  3. - (BOOL) canMyPrivateThingEatThis: (OtherClass *) aThing;  
  4. @end  
  5.   
  6. @implementation MyClass  
  7. ...  
  8. @end  


這些方法將不會在相應的@implementation MycClass (SuperSecretInternalSauce) 塊里被實現,當然它們也不是一定要被實現的。

 

但這樣做的結果是編譯器將不會做確保你實現了所有在category里聲明的方法的檢查,換句話說,編譯器也不會捕獲方法聲明中的拼寫錯誤。

這是因為category如果沒有相應@implementation MycClass (SuperSecretInternalSauce)實現塊,那么在objective-c里它就是一個非正式協議

它就是一個方法聲明集,里面的方法可以有選擇的去實現,通常這種category會被聲明在這個類的了類里。

由於 class extension 有效的擴展類的主接口,那么把上面的一段聲明代碼改成下面這樣,也可以達到同樣的效果。

 

 
  1. @interface MyClass ()  
  2. - (void) doMyPrivateThing;  
  3. - (BOOL) canMyPrivateThingEatThis: (OtherClass *) aThing;  
  4. @end  
  5.   
  6. @implementation MyClass  
  7. ...  
  8. @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:

  1. @interface MyClass : NSObject  
  2. @property(readonly) NSView *targetView;  
  3. @end  


然后是實現文件:

  1. @interface MyClass()  
  2. @property(readwrite) NSView *targetView;  
  3. @end  
  4.   
  5. @implementation MyClass  
  6. @synthesize targetView;  
  7. @end  


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM