1. 程式人生 > >類別和類擴充套件的區別

類別和類擴充套件的區別

在iOS中,有一種機制可以使使用者在沒有遠嗎的情況下擴充套件類的功能,但不是通過繼承,這就是類別。iOS中沒有類似C++中可以定義私有方法和私有變數的關鍵字,要定義私有方法和私有變數,可以用類擴充套件來實現。  
類別  
類別在不需要繼承的情況下可以擴充套件類的功能。但類別不能新增類的屬性和私有變數。類別可以用來擴充套件Cocoa中類的方法,也可以用來擴充套件使用者自己的類中的方法。當我們檢視系統標頭檔案的時候能發現類似@interface NSMutableArray (NSExtendedMutableArray)的類定義,其實這就是類別的定義形式。例如下面的程式碼,定義了一個NSString的類別,是用來做Base64的編碼和解嗎的。  

[cpp] view plaincopy  
@interface NSString (Base64)    

-(NSString *)encodeBase64;    
-(NSString *)decodeBase64;    

@end    
類別的定義和類的定義有相似之處,都是用關鍵字@interface和類名來定義,不同之處在於類別的定義是在類名之後不是類所繼承的父類,而是用括號括起來的類別名。@end之前的方法定義和類中方法的定義是一樣的。不過,在類別中,不能定義屬性。類別中的方法與原類中的方法的使用是完全一致的,沒有任何差別。所有NSString的子類也都能使用類這兩個類別中的方法。  

在使用類別的時候,類別中的方法命名特別重要。  
如果類別中的方法名與原類中的方法名重名了,在蘋果開發者文件中的描述是,當方法重名的時候,在執行的時候不知道會呼叫哪個方法。實際上,這個應該是有規則可循的。我建了一個工程,給NSString增加了一個類別,裡面重寫了length和substringFromIndex方法。length方法是NSString的方法,substringFromIndex是NSString的一個類別裡的方法。當我呼叫者兩個方法時,發現呼叫length的時候返回的是系統的那個呼叫,而不是我自己實現。而當呼叫substringFromIndex時,呼叫的則是我實現的方法。於是我推斷當系統類中的方法名與自己定義的類別裡的方法重名時,會呼叫系統的方法名,而當自定義類別中的方法名與系統類別中的方法重名時,會使用自定義類別中的方法的實現。為了驗證這一推斷,我又繼續增加了NSArray的類別來進行測試,測試結果正如我推斷的一樣。即使是這樣,我們也不能確定這個結論就是正確的,還有待進一步的驗證。  

在上面的Base64類別中,增加兩個方法  
[cpp] view plaincopy  
-(NSUInteger)length;    
-(NSString *)substringFromIndex:(NSUInteger)from;    
[cpp] view plaincopy  
實現這兩個方法    
[cpp] view plaincopy  
-(NSUInteger)length    
{    
    return 40;    
}    

-(NSString *)substringFromIndex:(NSUInteger)from    
{    
    return @"sub string";    
}    
在實現這兩個方法的時候,length方法會有一個警告,說這是原類中的方法,而第二個方法卻沒有,因為它是NSString的一個類別中的方法。將這兩個方法做自己的實現  
現在來試著呼叫一下  
[cpp] view plaincopy  
NSString *title = @"標題";    
NSLog(@"title length:%d", [title length]);    
NSLog(@"sub string from index 1: %@", [title substringFromIndex:1]);    
輸出結果是:  
[cpp] view plaincopy  
2013-08-16 00:19:30.678 CategoryTest[12088:c07] title length:2    
2013-08-16 00:19:30.679 CategoryTest[12088:c07] sub string from index 1: sub string    
length的結果沒有問題,而substring的方法就是呼叫了咱們實現的類別裡的方法。  
再來看看NSArray的類別定義  
[cpp] view plaincopy  
@interface NSArray (ArrayTest)    

- (NSUInteger)count;    
- (id)objectAtIndex:(NSUInteger)index;    

- (id)lastObject;    

@end    
前面兩個方法是NSArray自帶的方法,後面一個方法是NSArray類別裡的方法,將他們用自己的方式實現  
[cpp] view plaincopy  
@implementation NSArray (ArrayTest)    

- (NSUInteger)count    
{    
    return 4;    
}    
- (id)objectAtIndex:(NSUInteger)index    
{    
    return nil;    
}    

- (id)lastObject    
{    
    return [self objectAtIndex:0];    
}    

@end    
現在我們來呼叫一下這幾個方法  
[cpp] view plaincopy  
NSArray *array = [NSArray arrayWithObjects:@"object 1", @"object 2", nil];    
NSLog(@"array count: %d", [array count]);    
NSLog(@"array object at index 0:%@", [array objectAtIndex:0]);    
NSLog(@"array last object:%@", [array lastObject]);    
輸出結果如下:  
[cpp] view plaincopy  
2013-08-16 00:19:30.680 CategoryTest[12088:c07] array count: 2    
2013-08-16 00:19:30.681 CategoryTest[12088:c07] array object at index 0:object 1    
2013-08-16 00:19:30.681 CategoryTest[12088:c07] array last object:object 1    
上面的推斷是基於實現的是系統類的類別,如果是自己的類的類別呢,是不是也跟系統的一樣。經過測試,結果稍有不同。當類別中的方法名與類中的方法重名時,呼叫的是類別中的方法。如果多個類別中有相同的方法,這個就跟類別的編譯順序有關了,誰最後編譯就呼叫誰的方法。我試著改變過不同類別檔案的編譯順序,發現方法的呼叫也跟著變了。這個自己可以寫個類測試一下。  
類擴充套件  
類擴充套件跟類別的定義有點像,類擴充套件有點像無名的類別。如下定義  
[cpp] view plaincopy  
@interface Person ()    

@property (nonatomic, strong) NSString *address;    

@end    
上面的程式碼是定義了一個Person類的類擴充套件,它與類別的不同之處在於,括號裡不需要寫名字。同時也可以在類擴充套件中定義屬性以及私有變數。另一個不同之處在於,類擴充套件必須與類定義以及類的實現同時編譯,也就是說,類擴充套件只能針對自定義的類,不能給系統類增加類擴充套件。類擴充套件定義的方法必須在類的實現中進行實現。如果單獨定義類擴充套件的檔案並且只定義屬性的話,也需要將類實現檔案中包含進類擴充套件檔案,否則會找不到屬性的set和get方法。  
在Person的類實現Person.m中,需要將Person_PersionExtension.h包含進行,否則是無法使用address屬性的,測試的時候會崩潰,因為找不到setAddress方法。包含之後就一切正常了。