高見龍

iOS app/Ruby/Rails Developer & Instructor, 喜愛非主流的新玩具 :)

Category in Objective-C

image

OOP的精神之一,就是如果你想研發一台新款式的車子,你並不需要重新發明輪子,通常的做法會去繼承某個現有的”車子類別”,然後加上你要的功能跟屬性,改一改就變成一款新的車子可以來騙錢了。

不過有時候你想幫原來的類別加功能,但又不想動到原來的程式碼,例如你可能下載了某款功能超強的2D物理引擎程式碼,但因為某些小地方寫的不合你的需求,於是你便動手改原始碼來加功能。這當然沒問題,但萬一原作出新的版本,你要不就選擇維持自己原來的版本不update,不然就是update之後,你原來加在舊版本的程式碼得再重貼一次到新版。

Objective-C裡有個叫做category的東西可以幫你在現有的類別加上新功能,這樣一來上面這個問題就可以搞定了。跟別的程式語言比較起來,category的觀念有點像是在Ruby的mixin或是Python的open class,都是在不影響或修改原來的類別或模組的情況下去修改原有的功能。

舉個例子,因為NSObject是所有物件的源頭,但我想要加一個方法讓所有的子類別都可用(把要加的功能放在繼承階層的最源頭並不是好的設計,在這裡只是舉個例子而已)。程式碼這樣寫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// interface
@interface NSObject(MySuperObject)
-(void) printRetainCount;
+(void) sayHello;
@end

// implementation
@implementation NSObject(MySuperObject)

-(void) printRetainCount
{
  NSLog(@"The retain count is %d", [self retainCount]);
}

+(void) sayHello
{
  NSLog(@"Hello everybody!");
}

@end

這裡用的是在後面加個小括號以及category的名稱。要注意的是category只能加method(instance method或是class method都行),沒辦法增加instance variable(其實也不是完全不行,只是可能要用一些怪招,不過如果要做到這種程度,是不是該考慮直接用一般的繼承就好?)。使用起來的樣子:

1
2
3
4
5
6
7
8
9
10
11
// 建立Book物件
Book *b = [[Book alloc] init];

// instance method from category
[b printRetainCount];

// 用完放掉
[b release];

// class method from category
[Book sayHello];

上面這段程式碼如果一般的情況下,在編譯階段就會跳出警告(認不得printRetainCount跟sayHello這兩個方法),硬是執行的話就會直接錯誤。但因為我們已經有了category的加持,所以執行結果會是:

The retain count is 1
Hello everybody!

因為category是在原來的類別裡加功能,所以你可能會想萬一原來的類別裡有個跟你的category同名的方法怎麼辦? 答案是,會以category的定義為主,也就是原來類別裡的那個方法就被你蓋掉了,當然這不見得是你想要的結果。所以通常這種方法擴充的,會建議可以在前面加個prefix,避免跟原來的方法有重複而造成不幸的結果。

另外,前面有篇提到protocol的文章,也提到了informal protocol,其實它就是一種依附在NSObject上,但是沒有把功能實作出來的category。一般的protocol必需乖乖實作所有@required的方法(在Objective-C 2.0以前全部預設都是@required),但在informal protocol並沒有強制規定全部都要實作出來。事實上,在Objective-C 2.0之後才在protocol裡加進來的@optional語法,就是打算用來取代informal protocol的,在比較新版本的SDK大多都是用標準的protocol在寫了。

範例原始檔下載

Comments