類別(Categories)和擴展/匿名類別(extensions)及其延伸使用

類別(Category)
 
分類能夠做到的事情主要是:即使在你不知道一個類的源碼情況下,向這個類添加擴展的方法
 
此外,類別能夠保證你的實現類和其他的文件區分開。
 
1 #import “UIViewController.h”
2 @interface UIViewController(CustomView)
3 -(void)extMethod;
4 @end

 

 使用類別爲類添加方法(Add Methods to Classes)
 
通過在interface中聲明一個額外的方法並且在implementation 中定義相同名字的方法即可。分類的名字(也就是括號括起來的CustomView)表示的是:對於聲明於其他地方的這個類(UIViewController),在此處添加的方法是額外的,而不是表示這是一個新的類你不可以通過分類爲一個類添加額外的成員變量
 
在implementation中,引入頭文件的時候主要引用的方式是:
 
1 #import “UIViewController+CustomView.h”
2 @implementation UIViewController(CustomView)
3 -(void)extMethod;
4 @end

 

 另外,雖然Category不能夠爲類添加新的成員變量,但是Category包含類的所有成員變量,即使是@private的。Category可以重新定義新方法,也可以override繼承過來的方法。

  關於Category不能添加成員變量或調用@synthesize的原因:
在類中@property關鍵字會生成私有成員變量,和申明getter  setter方法,但是在分類中只會聲明getter和setter方法,不會生成成員變量(因爲變量需要存儲,而在category裏不能有效的聲明一個存儲區)。
所以、嚴格來說.Category中的@property代表的是真正意義上的屬性(屬性其實是set和get方法、而對應變量並非必須存在)
此外:如果在.m中手動實現了setter和getter方法,x-code就不會自動生成@synthesize,也就不會生成成員變量點語法訪問的是getter方法,對象的->調用纔是訪問的成員變量,如果我們真的需要給category增加變量的實現,需要藉助於運行時(runtime)的兩個函數來生成模擬變量
objc_setAssociatedObject
objc_getAssociat
edObject 


//NSObject+IndieBandName.h
@interface NSObject (IndieBandName)
@property (nonatomic, strong) NSString *indieBandName;
@end

上面是頭文件聲明,下面的實現的.m文件:

複製代碼
// NSObject+IndieBandName.m    
#import "NSObject+Extension.h"
#import <objc/runtime.h>
static const void *IndieBandNameKey = &IndieBandNameKey;    
@implementation NSObject (IndieBandName)
@dynamic indieBandName;

- (NSString *)indieBandName {
    return objc_getAssociatedObject(self, IndieBandNameKey);
}

- (void)setIndieBandName:(NSString *)indieBandName{
    objc_setAssociatedObject(self, IndieBandNameKey, indieBandName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
複製代碼

———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
延伸使用方法舉例與注意事項:

1、擴展一個其它實施者定義的類
   例如,你可以爲Cocoa frameworks裏的類增加方法。增加的方法會被子類繼承、而且在運行時也不會和原始的方法有任何不同。

2、作爲子類的一個替代方式
   不需要定義一個子類來擴展已有的類,通過category你可以直接爲類添加方法。
  例如,你可以爲NSArray和其它的Cocoa classes添加categories.與添加子類的方式來比,你不需要你擴展的類的源代碼。

3、把實現一個新類的方法分佈在多個源文件裏
   例如,你可以把一個很大的類的方法分組到幾個categories裏,然後把每個     category放在自己的文件裏。當以這種方式使用時,categories在很多方面對開發過程都是有幫助的:

   1.提供一個簡單的方式來組合相關的方法。被定義在不同的類裏的相似的方法可以被保存在同一個源文件裏。

        2.當一個類是由多個開發者共同定義的時候,可以簡化大類的管理。

   3.爲一個非常大的類的增量編譯提供方便。

   4.提高常用方法的本地參考。

   5.可以根據不同版本的程序配置不同的類,而無需爲不同版本保持相同的源代碼。

4、可以用來聲明非正式協議
   例如:@interface NSObject ( MyXMLSupport )

- initFromXMLRepresentation: (NSXMLElement *)XMLElement;

- ( NSXMLElement *)XMLRepresentation;

      @end
以上、由於是非正式協議。所以編譯器不會檢測協議中的語法、以及方法是否實現

5、root class類別

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

另外,當你爲root class實現方法時有兩點需要記住:

發送消息給super是非法的(因爲NSObject沒有超類

類的對象可以執行root class中定義的實例方法

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



———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
此外、雖然Objective-C語言目前允許使用category來通過重載繼承的類的方法或者甚至是類文件中的方法,但是這種做法是被強烈反對的。category不是子類的替代品。使用category來重載方法有很多重大的缺陷

1.當category 重載一個從父類繼承過來的方法,通常可以通過super關鍵字來調用父類的實現方法。然而,如果category重載一個擴展類本身存在的方法,就沒有喚醒原始實現方法的辦法了。
 
2.同一個類的category不能聲明重載這個類的另一個category中聲明的方法。
  這一點非常的重要,因爲很多Cocoa類也是通過使用categories來實現的。一個你試圖重載的框架中定義的方法可能本身就已經在一個category被實現了,如果你這樣做了,很可能使用得前面的category的方法的實現失效。

3.一些category methods的存在可能會導致整個框架的行爲發生變化。
   例如,如果你在NSObject的一個category中重載windowWillClose:委託方法,在你的程序裏所有窗口的委託將會使用category方法來回應;所有NSWIndow實例的行爲都會改變。你爲一個框架類增加的Categories可能會導致行爲上很神祕
的變化和程序的崩潰。


類擴展(Class Extensions)
 
類擴展就像匿名(也就是沒有那個括號裏面的名字CustomView)的分類一樣,除了一樣不同的是,類擴展聲明必須在相應的主@implementation代碼塊中實現。
 
先看一段代碼:
複製代碼
 1 @interface MyObject:NSObject
 2 {
 3 NSNumber* number;
 4 }
 5 -(NSNumber*)getNum;
 6 @end
 7  
 8 @interface MyObject(Setter)
 9 -(void)setNum:(NSNumber*)num;
10 @end
11  
12 @implementation MyObject
13 -(NSNumber*)getNum
14 {
15 return number;
16 }
複製代碼

 

 看上面這段代碼,有沒有問題?編譯器編譯的時候,這段代碼是可以編譯通過(有警告),但當運行時,就會報錯。爲什麼?
因爲沒有實現Category中的setNum方法。

而用類擴展去實現,請看:
複製代碼
 1 @interface MyObject:NSObject
 2 {
 3 NSNumber* number;
 4 }
 5 -(NSNumber*)getNum;
 6 @end
 7  
 8 @interface MyObject() //注意這裏的括號裏面是沒有名字的
 9 -(void)setNum:(NSNumber*)num;
10 @end
11  
12 @implementation MyObject
13 -(NSNumber*)getNum
14 {
15 return number;
16 }
17  
18 -(void)setNum:(NSNumber*)num
19 {
20 number = num;
21 }
22 @end
複製代碼

 

setNum是要實現的,不然編譯器會提出警告。
 
從上面看出,分類和類擴展的相似之處是:都可以爲類添加一個額外的方法、如未實現都會出現提示
 
不同之處在於:要添加額外方法,類別必須在第一個@interface中聲明方法,並且在相關的某個@implementation中提供實現(即使在主@implementation中實現、而未在類別的.m中未實現也會出警告、不過不影響)。而類擴展,方法的聲明可以不在第一個@interface中去聲明。(這裏特別指出、如果無法獲悉一個類的源碼,用擴展添加方法是不可取的。因爲extension只有.h文件,沒有自己的@implementation,必須到源碼的主@implementation模塊去實現方法)

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

關於Extensions的延伸用法

Extensions 被設計出來的目的是爲了解決二個問題。

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


1、有關更好的驗證類的私有接口:

當實現一個類,通常在類的@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中聲明的方法的實現,編譯器將會發出抱怨。

2、設計一個對外只讀、對內可讀可寫的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就完成了。
發佈了23 篇原創文章 · 獲贊 2 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章