2015年11月11日 星期三

[iOS] kkbox-ios-dev note  Category

Category:不用繼承物件,就直接增加新的method,或替換原本的method。

什麼時候應該要使用Category?
如果想要擴充某個class功能,增加新的成員變數或method,
我們又沒這個class的程式碼,正規作法就是繼承,建立新的subclass。
那,我們需要在不用繼承,就直接增加method這種作法的重要理由,
就是我們想要擴充的class很難繼承。

ex
1. Foundation物件
2. 用Factory Method Pattern實作的物件
3. Singleton物件
4. 在專案中出現次數已經多不勝數的物件。

Singleton物件:某個class只有,也只該有一個instance,每次都只對這個
instance操作。而不是建立新的instance。

Category的語法很簡單,一樣是用@interface關鍵字宣告header,
在@implementation與@end關鍵字當中的範圍是實作,
然後在原本的class名稱後面,用中括弧表示新增的category名稱。
@interface NSObject (testCategory)
- (void)printNl;
@end

@implementation NSObject (testCategory)

- (void)printNl {
    NSLog(@"%@", self);
}

@end
如此一來,每個物件都增加了printNl這個method可以這麼呼叫:
[myObject printNl]

在存檔的時候,檔名的慣例是原本的class名稱加上category的名稱, 中間用加號連接,以我們剛建立的範例為例。 就是NSObject+testCategory.h和NSObject+testCategory.m

Category還可以有什麼用途?
除了幫原有的class增加新的method,我們也會在幾種狀況下使用category。
1 . 將一個很大的Class切成幾個部分
2. 替換原本的實作(最好避免,因為不知道Objective-C runtime載入category的順序)

Extensions
Objective-C語言中有一項叫做extensions的設計,也可以用來拆分一個很大的class
語法與category非常相似,但是不太一樣,在語法上,extensions像是一個沒有名字的category,在class名稱之後直接加上空的括弧,而extensions定義的method, 需要放在原本的class實作中。
@interface MyClass : NSObject
@end

@interface MyClass()
- (void)doSomething;
@end

@implementation MyClass

- (void)doSomething {
    //doSomething
}

@end

extension的用途

1.拆分Header
如果我們就是打算實作一個很大的class,但是覺得header裡頭已經列出了太多method,我們可以將一部分method搬到extensions的定義裡頭。另外,extension除了可以放method之外,也可以放成員變數, 而一個class可以擁有不只一個extension,所以如果一個class真的有非常非常多的method與成員變數, 我們可以把這些method與成員變數、放在多個extension中。

2.管理Private Methods
這其實是更常見的用除,我們在寫一個class的時候,內部有一些method不需要,我們也不想要放在public header中 但是如果不將這些method放在header裡頭,又會出現一個困擾:在Xcode4.3之前,如果這些private method在程式碼中 不放在其它method前面,
其他的method在呼叫這些method的時候,compiler會不斷跳出警告,而這種無關緊要的警告一多,我們往往會忽視真正的警告。
想要避免這些警告,要不就是把private method都放在最前面,但這樣並不能完全解決問題,因為private method之間也會相互呼叫,花時間確認每個method之間的呼叫順序並不是很經濟的事;要不就是都用performSelector:呼叫,但這樣問題更大,就像前面提到,在method改名、呼叫refactoring工具的時候,這樣非常危險。
蘋果提供的建義是,我們在.m或.mm檔案開頭的地方宣告一個extensions,
將private method都放在這個地方,如此一來,其他method就可以找到private method的宣告。從Xcode4開始至少到Xcode7都是如此,在Xcode中所提供的file template中,如果你選擇建立一個UIViewController的subclass,就可以看到在.m檔案的最前面幫你預留了一塊extension的宣告。

Category是否可以增加新的成員變數或屬性?
因為Objective-C物件會被編譯成C的structure,我們雖然可以category中增加新的method,但是我們卻不能夠增加新的成員變數。
在Mac OS X 10.6與iOS4之後,蘋果提出一套叫做Associated Objects的辦法,讓我們可以在category中增加新的getter/setter,觀念差不多是:既然我們可以用一張表格記錄一個class有哪些method,那我們不就也可以另外建一張表格,記錄有哪些物件與這個class相關?
要使用Associated Objects,我們需要匯入objc/runtime.h然後呼叫objc_setAssociatedObject建立setter,用getAssociatedObject建立getter,呼叫時要傳入:我們要讓那個物件與那個物件之間建立關連,關連時使用的是那一個key(型別為C字串)。在以下範中,我們在MyCategory這個category裡,增加一個叫做myVar的property。

必須先#import <objc/runtime.h>

@interface MyClass(MyCategory)
@property (retain, nonatomic) NSString *myVar;
@end

@implementation MyClass
- (void)setMyVar:(NSString *)inMyVar {
    objc_setAssociatedObject(self, "myVar", inMyVar, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)myVar {
    return objc_getAssociatedObject(self, "myVar");
}

@end


在setMyVar:中呼叫objc_setAssociattedObject時,最後一個參數OBJC_ASSOCIATION_RETAIN_NONATOMIC,是用來決定要用那一種記憶管理策略,管理我們傳入的參數,在我們的例子中,我們傳入的是一個NSString,是一個Objective-C物件,所以我們必須要retain起來。這邊可以傳入的參數還可以是OBJC_ASSOCIATION_ASSIGN、OBJC_ASSOCIATION_COPY_NONATOMIC、OBJC_ASSOCIATION_RETAIN以及OBJC_ASSOCIATION_COPY,與property語法使用的記憶體管理方法一致。而當我們的MyClass物件在dealloc的時候,所有透過objc_setAssociatedObject而retain起來的物件,也都會被一併釋放。

沒有留言:

張貼留言