iOS 設計模式-你可能已經聽說過這個詞,但是你真正理解它意味著什麼嗎?雖然大多數的開發者可能都會認為設計模式是非常重要的,然而關於設計模式這一主題的文章卻不多,並且有時候我們開發者在寫代碼的時候也不會太關注它。
在軟件設計領域,設計模式是對通用問題的可復用的解決方案。設計模式是一系列幫你寫出更可理解和復用代碼的模板,設計模式幫你創建松耦合的代碼以便你不需要費多大力就可以改變或者替換代碼中的組件。
如果你剛接觸設計模式,我們有好消息告訴你!首先,多虧了Cocoa的構建方式,你已經使用了許多的設計模式以及被鼓勵的最佳實踐。
其次本指南將帶你使用絕大多數(並不是所有)Cocoa中頻繁使用的IOS 設計模式。
本指南被分為了許多部分,每個部分涉及一個設計模式。在每個部分中,你將會了解到如下內容:
? 設計模式是什麼?
? 你為什麼要用設計模式?
? 如何使用設計模式,以及在使用的時候,哪裡是合適的,哪裡是需要注意的坑。
在本指南中,你將創建一個音樂庫應用,這個應用將顯示你的專輯以及它們相關聯的信息。
在開發本應用的過程中,你將熟悉被大量使用的Cocoa 設計模式:
? 創建型:單利(單態)和 抽象工廠
? 結構型:模型-視圖-控制器,裝飾器,適配器,外觀(門面)和組合模式
? 行為型:觀察者,備忘錄,責任鏈和命令模式
不要被誤導認為這是一篇關於設計模式理論的文章,在本音樂應用中,你將使用這些設計模式中的大多數,最終你的音樂應用將長的像下圖所示的那樣:
我們開始吧!<喎?/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPgoKz8LU2CBzdGFydGVyCiBwcm9qZWN0LLW8s/Z6aXDOxLz+tcTE2sjdo6zIu7rz08N4Y29kZbTyv6pCbHVlTGlicmFyeS54Y29kZXByb2ouCgq5pLPMwO/D5sO709DMq7bgtcTOxLz+o6y99r32sPy6rMixyqG1xFZpZXdDb250cm9sbGVy0tS8sL/VyrXP1rXESFRUUCBDbGllbnQuCgrXotLio7q1scTjtLS9qNK7uPbQwrXEWGNvZGW5pLPMtcTKsbryo6zE47XEtPrC68bkyrXS0b6tyea8sLW9wcvJ6LzGxKPKvaOsxOPWqrXAwvCjv8Sj0M0tytPNvC2/2NbGxvejrM6vzdCjrNCt0umjrLWlwP0txOOyu7fRtLW70tauwaa+zb/J0tTD4rfRyrnTw8v8w8fAsqGjCgrU2sTjye7I67W9tdrSu7j2yei8xsSjyr3Wrsewo6zE48rXz8ix2NDrtLS9qMG9uPbA4KOs08PV4sG9uPbA4MilsaO05rrNz9TKvtL0wNa/4teovK21xNDFz6KhowoK1NpYY29kZdbQo6y1vLq9tb0="File\New\File..."(或者按Command+N快捷鍵),選擇IOS>Cocoa
Touch,然後Objective-C class,點擊下一步。設置類名稱為Album,父類選擇NSObject,點擊下一步,然後創建。
打開Album.h文件,在@interface和@end之間,增加如下的屬性和方法原型:
Objective -c代碼
-
@property (nonatomic, copy, readonly) NSString *title, *artist, *genre, *coverUrl, *year;
-
- (id)initWithTitle:(NSString*)title artist:(NSString*)artist coverUrl:(NSString*)coverUrl year:(NSString*)year;
注意到新增代碼中所有的屬性都是只讀的,因為在Album對象創建以後,不需要修改它們的值。
新增的方法是對象初始化器(object initializer),當你創建一個新的專輯(album)對象的時候,你需要傳遞專輯(album)名,藝術家,專輯封面URL,以及年份。
現在打開Album.m文件,在@implementation 和 @end 之間 增加如下代碼:
Objective-c代碼
-
- (id)initWithTitle:(NSString*)title artist:(NSString*)artist coverUrl:(NSString*)coverUrl
-
year:(NSString*)year {
-
self = [super init];
-
if (self)
-
{
-
_title = title;
-
_artist = artist;
-
_coverUrl = coverUrl;
-
_year = year;
-
_genre = @"Pop";
-
}
-
return self;
-
}
這裡沒什麼復雜花哨的東西,僅僅是一個創建Album實例的初始化方法而已。
在Xcode中,再一次導航到"File\New\File..."選擇Cocoa
Touch,然後Objective-C class,點擊下一步。設置類名為AlbumView,但是這一次設置父類為UIView。點擊下一步然後點擊創建。
注意:如果你發現鍵盤快捷鍵更容易使用,Command+N將創建一個新文件,Command+Option+N將創建一個新組,Command+B將構建你的工程,Command
+ R 將運行它。
現在打開AlbumView.h,在@interface 和 @end之間 增加如下的方法原型:
Objective-c代碼
-
- (id)initWithFrame:(CGRect)frame albumCover:(NSString*)albumCover;
現在打開AlbumView.m,用如下代碼替換@implementation 之後所有的代碼:
Objective-c代碼
-
@implementationAlbumView
-
{
-
UIImageView *coverImage;
-
UIActivityIndicatorView *indicator;
-
}
-
-
- (id)initWithFrame:(CGRect)frame albumCover:(NSString*)albumCover
-
{
-
self = [super initWithFrame:frame];
-
if (self)
-
{
-
-
self.backgroundColor = [UIColor blackColor];
-
// the coverImage has a 5 pixels margin from its frame
-
coverImage = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, frame.size.width-10,
-
-
frame.size.height-10)];
-
[self addSubview:coverImage];
-
-
indicator = [[UIActivityIndicatorView alloc] init];
-
indicator.center = self.center;
-
indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
-
[indicator startAnimating];
-
[self addSubview:indicator];
-
}
-
return self;
-
}
-
-
@end
上面的代碼裡,你首先需要注意的是coverImage實例變量。它表示這個專輯的封面圖。第二個變量是一個通過旋轉來指示封面圖正在下載的指示器。
在初始化器的實現中你設置背景顏色為黑色,創建了有5像素邊框的圖片視圖,同時還創建了指示器。
注意:你可能想知道為什麼私有變量在實現文件中定義,而不是在接口文件中?這是因為AlbumView以外的類不需要知道這些變量的存在,這些變量僅僅只在類內部函數使用。如果你在開發給其它開發者使用的框架,這個約定就顯得十分重要了。
構建(Command + B)你的工程確保每件事情都井井有條,都ok嗎?然後准備迎接我們的第一個設計模式!
模型-視圖-控制器(MVC)模式 - 設計模式之王
模型-視圖-控制器(MVC) 是Cocoa的構建塊之一,毫無疑問它是使用最頻繁的設計模式。它根據通用的角色去劃分類,這樣就使得類的
職責可以根據角色清晰的劃分開來。
涉及到的三個角色如下:
Model:
模型保存應用程序的數據,定義了怎麼去操作它。例如在本應用中模型就是Album類。
View:
視圖是模型的可視化表示以及用戶交互的控件;基本上來說,所有的UIView對象以及它的子類都屬於視圖。在本應用中AlbumView代表了視圖。
Controller:
控制器是一個協調所有工作的中介者(Mediator)。它訪問模型中的數據並在視圖中展示它們,同時它們還監聽事件和根據需要操作數據。你可以猜猜哪個類是控制器嗎?它正是:ViewController。
一個MVC模式的好的實現也就意味著每一個對象都會被劃分到上面所說的組中。
我們可以很好的用下圖來描述通過控制器實現的視圖到模型的交互過程:
模型會把任何數據的變更通知控制器,然後控制器更新視圖數據。視圖對象通知控制器用戶的操作,控制器要麼根據需要來更新模型,要麼檢索任何被請求的數據。
你可能在想為什麼不能僅僅使用控制器,在一個類中實現視圖和模型,這樣貌似更加容易?
所有的這些都歸結於代碼關注點分離以及復用。在理想的狀態下,視圖應該和模型完全的分離。如果視圖不依賴某個實際的模型,那麼視圖就可以被復用來展示不同模型的數據。
舉個例子來說,如果將來你打算加入電影或者書籍到你的資料庫中,你仍然可以使用同樣的AlbumView去顯示電影和書籍數據。更進一步來說,如果你想創建一個新的與專輯有關聯的工程,你可以很簡單的復用Album類,因為它不依賴任何視圖。這就是MVC的強大之處。
如何使用MVC模式
首先,你需要確保在你工程中的每個類是控制器,模型和視圖中的一種,不要在一個類中組合兩種角色的功能。到目前為止,你創建了一個Album類和AlbumView類,這樣做挺好的。
其次,為了確保你能符合這種工作方法,你應該創建三個工程組(Project Group)來保存你的代碼,每個工程組只存放一種類型的代碼。
導航到"文件\新建\組(File\New\Group)"(或者按下Command
+ Option + N),命名組為Model,重復同樣的過程來創建View和Controller組。
現在拖動Album.h和Album.m去模型組,拖動AlbumView.h和AlbumView.m去視圖組,最後拖動ViewController.h和ViewController.m到控制器組。
此時工程結構應該看起來和下圖類似:
沒有了之前所有文件都散落在各處,現在你的工程已經開起來好多了。顯然你也可以有其它的組和類,但是本應用的核心包含在這三個類別中(Model,View,Controller)。
現在所有的組件都已經安排好了,你需要從某處獲取專輯數據。你將創建一個貫穿於代碼的管理數據的API-這也就代表將有機會去討論下一個設計模式 - 單例(單態)模式。
單例(單態)模式
單例設計模式確保對於一個給定的類只有一個實例存在,這個實例有一個全局唯一的訪問點。它通常采用懶加載的方式在第一次用到實例的時候再去創建它。
注意:蘋果大量使用了此模式。例如:[NSUserDefaults standardUserDefaults],[UIApplication
sharedApplication], [UIScreen mainScreen], [NSFileManager defaultManager],所有的這些方法都返回一個單例對象。
你很可能會想為什麼這麼關心是否一個類有多個實例?畢竟代碼和內存都是廉價的,對嗎?
有一些情況下,只有一個實例顯得非常合理。舉例來說,你不需要有多個Logger的實例,除非你想去寫多個日志文件。或者一個全局的配置處理類:實現線程安全的方式訪問共享實例是容易的,比如一個配置文件,有好多個類同時修改這個文件。
如何使用單例模式
首先來看看下面的圖:
上面的圖描述了一個有單一屬性(它就是單一實例)和sharedInstance,init兩個方法的類。
客戶端第一次發送sharedInstance消息的時候,instance屬性尚未被初始化,所以此時你需要創建一個新的實例,然後返回它的引用。
當你下一次調用sharedInstance的時候,instance不需要任何初始化可以立即返回。這個邏輯保證總是只有一個實例。
你接下來將用這個模式來創建一個管理所有專輯數據的類。
你將注意到工程中有一個API的組,在這個組裡你可以放入給你應用提供服務的所有類。在此組中,用IOS\Cocoa Touch\Objective-C class 模板創建一個新類,命名它為LibraryAPI,設置父類為NSObject.
打開LibraryAPI.h,用如下代碼替換它的內容:
Objective-c代碼
-
@interfaceLibraryAPI : NSObject
-
-
+ (LibraryAPI*)sharedInstance;
-
-
@end
現在打開LibraryAPI.m,在@implementation 那一行後面插入下面的方法:
Objective-c代碼
-
+ (LibraryAPI*)sharedInstance
-
{
-
// 1
-
static LibraryAPI *_sharedInstance = nil;
-
-
// 2
-
static dispatch_once_t oncePredicate;
-
-
// 3
-
dispatch_once(&oncePredicate, ^{
-
_sharedInstance = [[LibraryAPI alloc] init];
-
});
-
return _sharedInstance;
-
}
在這個簡短的方法中,有一些需要需要注意的點:
1.聲明一個靜態變量去保存類的實例,確保它在類中的全局可用性。
2.聲明一個靜態變量dispatch_once_t ,它確保初始化器代碼只執行一次
3.使用Grand Central Dispatch(GCD)執行初始化LibraryAPI變量的block.這 正是單例模式的關鍵:一旦類已經被初始化,初始化器永遠不會再被調用。
下一次你調用sharedInstance的時候,dispatch_once塊中的代碼將不會執行(因為它已經被執行了一次),你將得到原先已經初始化好的實例。
注意: 為了學習更多關於GCD方面的信息以及如何使用,請查看本站指南Multithreading
and Grand Central Dispatch 和 How
to Use Blocks。
你現在有一個單例的對象作為管理專輯數據的入口。咋們更進一步來創建一個處理資料庫數據持久化的類。
在API組中,使用iOS\Cocoa Touch\Objective-C class 模板 創建一個新類,命名它為PersistencyManager,設置父類為NSObject.
打開PersistencyManager.h 在文件頭部增加下面的導入語句:
#import "Album.h"
接下來,在PersistenceManager.h文件的@interface之後,增加下面的代碼:
Objective-c代碼
-
- (NSArray*)getAlbums;
-
- (void)addAlbum:(Album*)album atIndex:(int)index;
-
- (void)deleteAlbumAtIndex:(int)index;
上面是你需要處理專輯數據的方法的原型。
打開PersistencyManager.m文件,在@implementation行之前,增加下面的代碼:
Objective-c代碼
-
@interfacePersistencyManager () {
-
// an array of all albums
-
NSMutableArray *albums;
-
}
上面增加了一個類擴張(class extension),這是另外一個增加私有方法和變量以至於外部類不會看到它們的方式。這裡,你申明了一個數組NSMutableArry 來保存專輯數據。這個數組是可變的方便你增加和刪除專輯。
現在在PersistencyManager.m文件中@implementation行之後增加如下代碼:
Objective-c代碼
-
- (id)init
-
{
-
self = [super init];
-
if (self) {
-
// a dummy list of albums
-
albums = [NSMutableArrayarrayWithArray:
-
@[[[Album alloc] initWithTitle:@"Best of Bowie" artist:@"David Bowie" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_david%20bowie_best%20of%20bowie.png" year:@"1992"],
-
[[Album alloc] initWithTitle:@"It's My Life" artist:@"No Doubt" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_no%20doubt_its%20my%20life%20%20bathwater.png" year:@"2003"],
-
[[Album alloc] initWithTitle:@"Nothing Like The Sun" artist:@"Sting" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_sting_nothing%20like%20the%20sun.png" year:@"1999"],
-
[[Album alloc] initWithTitle:@"Staring at the Sun" artist:@"U2" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_u2_staring%20at%20the%20sun.png" year:@"2000"],
-
[[Album alloc] initWithTitle:@"American Pie" artist:@"Madonna" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_madonna_american%20pie.png" year:@"2000"]]];
-
}
-
return self;
-
}
在init中,你用五條樣例專輯填充數組。如果你不喜歡上面的專輯,你可以自由用你喜歡的專輯替換它們。
現在在PersistencyManager.m文件中增加下面的三個方法:
Objective-c代碼
-
- (NSArray*)getAlbums
-
{
-
return albums;
-
}
-
-
- (void)addAlbum:(Album*)album atIndex:(int)index
-
{
-
if (albums.count >= index)
-
[albums insertObject:album atIndex:index];
-
else
-
[albums addObject:album];
-
}
-
-
- (void)deleteAlbumAtIndex:(int)index
-
{
-
[albums removeObjectAtIndex:index];
-
}
這些方法讓你可以增加和刪除專輯。
構建你的工程確保每個資源都可以被正確的編譯。
這時候,你可能想知道PersistencyManager類來自哪裡?因為它不是一個單例類。下一部分,我們將探究LibraryAPI 和PersistencyManager之間的關系,那時候你將看到門面或者外觀(Facade)模式