今天呢,我又要開啟我的bibi模式了,首先我給大家出個問題:假如有一個需求是讓你擴充類,這時候你會怎么做?
可能我們想到最多的就是使用繼承。其實啊!在OC中有一種除了繼承之外的另一種方法:分類(Category)。
那什么是分類呢?
簡單來說,就是在不改變原先類前提下,我們可以添加咱們自定義的方法,這樣和同事合作開發的時候,是不是順暢的多啦?但是使用分類的時候不能向原先類中增加成員變量,分類方法實現中可以訪問原來類中的成員變量。
下面我先舉一個簡單的例子:
現在有一個Person類,里面有一個study方法,現在想在不改變Person類的基礎上添加一個test方法。那么我們就可以新建一個分類如下:
示例代碼如下:
1 //Person.h
2 #import <Cocoa/Cocoa.h>
3
4 @interface Person : NSObject 5 - (void)study; 6 @end
7
8 //Person.m
9
10 #import "Person.h"
11
12 @implementation Person 13 - (void)study 14 { 15 NSLog(@"Person 類中的study方法"); 16 } 17 @end
18
19 //分類Person+QYMa.h
20 #import "Person.h"
21
22 @interface Person (QYMa) 23 - (void)test; 24 @end
25
26 //Person+QYMa.m
27 #import "Person+QYMa.h"
28
29 @implementation Person (QYMa) 30 - (void)test 31 { 32 NSLog(@"Person+QYMa 中的test方法"); 33 } 34 @end
35 //main方法 測試
36 #import <Foundation/Foundation.h>
37 #import "Person.h"
38 #import "Person+QYMa.h"
39
40 int main(int argc, const char * argv[]) { 41 @autoreleasepool { 42
43 Person *p = [[Person alloc]init]; 44 [p study]; 45 [p test]; 46 NSLog(@"Hello, World!"); 47 } 48 return 0; 49 }
測試結果:
通過這個示例,咱們來細說一下分類的、聲明實現格式:
1 @interface 類名(分類名稱) //建議分類名稱為模塊名稱 2 //方法的聲明
3 @end
1 @interface 類名(分類名稱) //建議分類名稱為模塊名稱 2 //方法的聲明
3 @end
接下來咱們來說說,分類一般的使用場景
- 在定義類的某些情況下(例如需求變更),你可能想要為其中的某個或幾個類中添加新的方法。
- 一個類中包含了許多不同種類的方法需要實現,而這些方法需要不同團隊的成員實現。
- 在使用基礎類庫的類時,有可能希望這些類實現一些自己需要的方法。
在這里呢,需要說一下如果出現相同方法名的方法的時候,優先在分類中找,然后在原來類中找,最后再父類。分類可以實現原來類中的方法,相當於覆蓋了原來的方法,使原來類中的方法失效。所以不建議同名。那么還有一種情況:假如有兩個分類同名方法都有實現的話要怎么調用?這個就和文件編譯順序有關,最后編譯的會覆蓋之前的,所以執行的是最后編譯的那個一個。
好接下來,我們舉一個擴充NSString類方法的例子:要求實現:計算某個字符串中阿拉伯數字的個數。
我們先新建一個分類:
示例代碼如下:
1 //NSString+Number.h 2 #import <Foundation/Foundation.h> 3 4 @interface NSString (Number) 5 + (int)numberCountofString:(NSString *)str; 6 - (int)numberCount; 7 @end 8 9 //NSString+Number.m 10 #import "NSString+Number.h" 11 12 @implementation NSString (Number) 13 + (int)numberCountofString:(NSString *)str 14 {//1.定義變量計算數字的個數 15 // int countNum = 0; 16 // for (int i = 0; i < str.length; i++) 17 // { 18 // unichar c = [str characterAtIndex:i]; 19 // if (c >= '0' && c <= '9') 20 // { 21 // countNum++; 22 // } 23 // } 24 // return countNum; 25 return [str numberCount]; 26 } 27 28 - (int)numberCount 29 { 30 int count = 0; 31 for (int i = 0; i < self.length; i++) 32 { 33 unichar c = [self characterAtIndex:i]; 34 if (c >= '0' && c <= '9') 35 { 36 count++; 37 } 38 } 39 return count; 40 } 41 @end 42 //main 測試代碼 43 #import <Foundation/Foundation.h> 44 #import "NSString+Number.h" 45 int main(int argc, const char * argv[]) { 46 @autoreleasepool { 47 48 int num = [NSString numberCountofString:@"1213Qweq1"]; 49 // int num = [@"1213Qweq1" numberCount]; 50 NSLog(@"字符串中阿拉伯數字的個數:%d",num); 51 } 52 return 0; 53 }
測試結果如下:
通過這兩個例子,估計分類我們也理解的差不多了。下面咱們來說說繼承和分類的區別:
Category是對一個功能完備的類的一種補充,就像是一個東西的主要基本功能都完成了,可以用category為這個類添加不同的組件,使得 這個類能夠適應不同情況的需求(但是這些不同需求最核心的需求要一致)。找個就像你已經有了一輛能夠開動的汽車一樣,我們可以用Category為你的汽 車添加各種之前沒有的功能,最后讓這輛汽車變成超級跑車一樣。當某個類非常大的時候,Category可以按不同的功能將類的實現分在不同的模塊中實現。
繼承則是都可以完成上面的工作,但是繼承有很大的代價問題,一是通過繼承來進行擴展是一種耦合很高的行為,對父類可以說是完全依賴;二是繼承由於 對父類依賴,所以開發代價相對大,要求對父類的工作流程相對熟悉;三是繼承體系如果太復雜會導致整個系統混亂,難以維護。所以在能夠分類的時候,就千萬不要使用繼承。什么情況才是迫不得已要使用繼承呢?那就是如果你既想提供一系列接口的定義,同時又想提供一些但是又不能提供全部的實現的 時候,這種情況就要使用繼承了。
雖然Category可以訪問類的實例變量,去不能創建新的實例變量,如果要創新的實例變量,請使用繼承。
在Category中,不提倡對原有方法進行重載。原因非常簡單,在Category中進行重載,無法對原方法進行訪問,而繼承中可以使用super。如果真的需要對原方法進行重載,請考慮繼承。
一個類可以定義多個Category,但是如果不同Category中存在相同方法,編譯器無法決定使用哪個Category;
在定義Category時,我們可以僅僅給出方法定義,而不需要給出具體的實現。這在程序增量開發時是非常有幫助的;
Category是可以被繼承的。在某個父類中定義了Category,那么他所有的子類都具有該Category;
在需要為某個類創建私有成員方法時,也用Category的方式來實現。
Category不能完全代替子類,有以下幾個最大的缺點:
當在Category中覆蓋一個繼承的方法,在Category中的方法可以通過向super類發送一個消息來調用被繼承的方法。但是,如果Category中覆蓋的那個方法已經在這個類的其它Category定義過了,則之前定義的方法將沒有機會被程序調用。
在Category中無法確定其能夠可靠的覆蓋某個方法,而這個方法已經在其它的Category中定義過。這個問題在使用Cocoa框架時尤其 突出。當你想覆蓋某個框架已經定義好的方法時,該方法已經在其它Category中實現,這樣就無法確定哪個定義和實現會被最先使用,帶來很大的不確定 性。
如果你重新覆蓋定義了一些方法,往往會導致這個方法在整個框架中實現發生了變化。
所以鑒於以上原因,在選擇繼承還是分類的時候,多考慮一些因素。祝大神們都寫出更好的代碼!
好啦!今天的bibi,就到此結束啦!感慨一下,外面還在下雨。-_-#。還能不能愉快的玩耍了?