類簇(class cluster)是一種設計模式,在Foundation Framework中被廣泛使用,舉個簡單的例子
1 2 3 |
NSArray *arr = [NSArray arrayWithObjects:@"foo",@"bar", nil]; NSLog(@"arr class:%@", [arr class]); // output: __NSArrayI |
顯然__NSArrayI
是一個私有類,來看看這個類的標頭檔案
1 2 3 4 5 |
@interface __NSArrayI : NSArray { unsigned int _used; } //... |
可以看出__NSArrayI
繼承了NSArray
。為什麼要這麼設計呢?拿NSNumber來舉個例子,我們都知道NSNumber可以儲存多種型別的數字,如Int/Float/Double等等,一種方式是把NSNumber作為基類,然後分別去實現各自的子類,像這樣:
初看起來也沒什麼問題,但如果子類很多,像這樣:
這對使用者來說顯然不夠方便,得記住這麼多類。如果使用類簇,問題就變得簡單了,把Number作為抽象基類,子類各自實現存取方式,然後在基類中定義多個初始化方式,像這樣:
現在只需要記住一個類就可以了。NSNumber
的初始化虛擬碼大概像這樣:
1 2 3 4 5 6 7 8 9 10 11 |
- (id)initWithBool { return [[__NSCFBoolean alloc]init]; } - (id)initWithLong { return [[__NSCFNumber alloc]init]; } //... |
在iOS專案中的應用
在開發app時經常會遇到表現和行為完全一樣,但資料來源不一樣的情況。以花瓣app為例,同樣是瀑布流,可能來自我喜歡的圖片、某個畫板下的圖片、某個使用者的圖片等等。如果為每一種表現方式都新建一個Controller,並且使用這個Controller來初始化,那麼就會遇到最開始提到的問題:子類太多,使用不便。這正好可以通過類簇來很方便地搞定。比如這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
@implementation HBWaterfallViewController - (id)initWithLiked { return [[HBLikedViewController alloc]init]; } - (id)initWithBoardID:(NSInteger)boardID { return [[HBBoardViewController alloc]initWithBoardID:boardID]; } #pragma mark - 通用的方法 - (PSUICollectionViewCell *)collectionView:(PSUICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { // ... } // ... #pragma mark - 每個子類需要實現的方法 - (void)fetchMoreData { NSAssert(NO, @"子類需要實現此方法"); } |
使用起來類似這樣[[HBWaterfallViewController alloc]initWithBoardID:9527]
或[[HBWaterfallViewController alloc]initWithLiked]
。如果有新的DataSource,新加一個初始化方法即可,對於使用者來說,開啟標頭檔案,看下init開頭的方法就行了。
再舉個例子,現在很多應用需要同時兼顧iOS6和iOS7,在表現上需要為不同的系統載入不同的圖片資源,最簡單粗暴的方法就是各種if/else判斷,像這樣:
1 2 3 4 5 6 7 8 |
if ([[UIDevice currentDevice]systemMajorVersion] < 7) { /* iOS 6 and previous versions */ } else { /* iOS 7 and above */ } |
不夠優雅,可以使用類簇的思想來去掉if/else判斷,把跟檢視具體元素無關的程式碼放在基類,跟系統版本相關的程式碼拆成兩個子類,然後在各自的類中載入相應的資源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
/* TestView.h */ @interface TestView: UIView /* Common method */ - ( void )test; @end /* TestView.m */ @implementation TestView + (id)alloc { if ([self class]== [TestView class]) { if ([[UIDevice currentDevice] systemMajorVersion] < 7) { return [TestViewIOS6 alloc]; } else { return [TestViewIOS7 alloc]; } } else { return [super alloc]; } } - ( void )test {} @end |
這裡alloc
時並沒有返回TestView
類,而是根據系統版本返回TestViewIOS6
或 TestViewIOS7
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/* TestViewIOS6.m */ @implementation TestViewIOS6: TestView - (void)drawRect: (CGRect)rect { /* Custom iOS6 drawing code */ } @end /* TestViewIOS7.m */ @implementation TestViewIOS7 - (void)drawRect: (CGRect)rect { /* Custom iOS7 drawing code */ } @end |
小結
類簇的本質其實是抽象工廠,類簇也可以有多個基類,如NSArray
, NSMutableArray
, 後者就是繼承的前者。它對一些「大同小異」的問題,往往會有不錯的效果。