iOS設計模式之一(MVC模式,單例模式)數組
iOS 設計模式-你可能已經據說過這個詞,可是你真正理解它意味着什麼嗎?雖然大多數的開發者可能都會認爲設計模式是很是重要的,然而關於設計模式這一主題的文章卻很少,而且有時候咱們開發者在寫代碼的時候也不會太關注它。
在軟件設計領域,設計模式是對通用問題的可複用的解決方案。設計模式是一系列幫你寫出更可理解和複用代碼的模板,設計模式幫你建立鬆耦合的代碼以便你不須要費多大力就能夠改變或者替換代碼中的組件。
若是你剛接觸設計模式,咱們有好消息告訴你!首先,多虧了Cocoa的構建方式,你已經使用了許多的設計模式以及被鼓勵的最佳實踐。
其次本指南將帶你使用絕大多數(並非全部)Cocoa中頻繁使用的IOS 設計模式。
本指南被分爲了許多部分,每一個部分涉及一個設計模式。在每一個部分中,你將會了解到以下內容:
設計模式是什麼?
你爲何要用設計模式?
如何使用設計模式,以及在使用的時候,哪裏是合適的,哪裏是須要注意的坑。
在本指南中,你將建立一個音樂庫應用,這個應用將顯示你的專輯以及它們相關聯的信息。
在開發本應用的過程當中,你將熟悉被大量使用的Cocoa 設計模式:
建立型:單利(單態)和 抽象工廠
結構型:模型-視圖-控制器,裝飾器,適配器,外觀(門面)和組合模式
行爲型:觀察者,備忘錄,責任鏈和命令模式
不要被誤導認爲這是一篇關於設計模式理論的文章,在本音樂應用中,你將使用這些設計模式中的大多數,最終你的音樂應用將長的像下圖所示的那樣:
咱們開始吧!
工程裏面沒有太多的文件,僅僅包含缺省的ViewController以及空實現的HTTP Client.
注意:當你建立一個新的Xcode工程的時候,你的代碼其實已經涉及到了設計模式,你知道嗎?模型-視圖-控制器,委託,協議,單例-你不費吹灰之力就能夠無償使用它們啦。
在你深刻到第一個設計模式以前,你首先必須建立兩個類,用這兩個類去保存和顯示音樂庫專輯的信息。
在Xcode中,導航到"File\New\File..."(或者按Command+N快捷鍵),選擇IOS>Cocoa Touch,而後Objective-C class,點擊下一步。設置類名稱爲Album,父類選擇NSObject,點擊下一步,而後建立。
打開Album.h文件,在@interface和@end之間,增長以下的屬性和方法原型:
@property (nonatomic, copy, readonly) NSString *title, *artist, *genre, *coverUrl, *year; xcode
- (id)initWithTitle:(NSString*)title artist:(NSString*)artist coverUrl:(NSString*)coverUrl year:(NSString*)year; 安全
注意到新增代碼中全部的屬性都是隻讀的,由於在Album對象建立之後,不須要修改它們的值。
新增的方法是對象初始化器(object initializer),當你建立一個新的專輯(album)對象的時候,你須要傳遞專輯(album)名,藝術家,專輯封面URL,以及年份。
如今打開Album.m文件,在@implementation 和 @end 之間 增長以下代碼:
- (id)initWithTitle:(NSString*)title artist:(NSString*)artist coverUrl:(NSString*)coverUrl app
year:(NSString*)year { 框架
self = [super init]; ide
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之間 增長以下的方法原型:
- (id)initWithFrame:(CGRect)frame albumCover:(NSString*)albumCover;
如今打開AlbumView.m,用以下代碼替換@implementation 以後全部的代碼:
@implementationAlbumView
{
UIImageView *coverImage;
UIActivityIndicatorView *indicator;
}
- (id)initWithFrame:(CGRect)frame albumCover:(NSString*)albumCover
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = [UIColor blackColor];
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,用以下代碼替換它的內容
@interfaceLibraryAPI : NSObject
+ (LibraryAPI*)sharedInstance;
@end
如今打開LibraryAPI.m,在@implementation 那一行後面插入下面的方法:
+ (LibraryAPI*)sharedInstance
{
static LibraryAPI *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[LibraryAPI alloc] init];
});
return _sharedInstance;
}
在這個簡短的方法中,有一些須要須要注意的點:
1.聲明一個靜態變量去保存類的實例,確保它在類中的全局可用性。
2.聲明一個靜態變量dispatch_once_t ,它確保初始化器代碼只執行一次
3.使用Grand Central Dispatch(GCD)執行初始化LibraryAPI變量的block.這 正是單例模式的關鍵:一旦類已經被初始化,初始化器永遠不會再被調用。
下一次你調用sharedInstance的時候,dispatch_once塊中的代碼將不會執行(由於它已經被執行了一次),你將獲得原先已經初始化好的實例。
你如今有一個單例的對象做爲管理專輯數據的入口。咋們更進一步來建立一個處理資料庫數據持久化的類。
在API組中,使用iOS\Cocoa Touch\Objective-C class 模板 建立一個新類,命名它爲PersistencyManager,設置父類爲NSObject.
打開PersistencyManager.h 在文件頭部增長下面的導入語句:
#import "Album.h"
接下來,在PersistenceManager.h文件的@interface以後,增長下面的代碼:
- (NSArray*)getAlbums;
- (void)addAlbum:(Album*)album atIndex:(int)index;
- (void)deleteAlbumAtIndex:(int)index;
上面是你須要處理專輯數據的方法的原型。
打開PersistencyManager.m文件,在@implementation行以前,增長下面的代碼:
@interfacePersistencyManager () {
NSMutableArray *albums;
}
上面增長了一個類擴張(class extension),這是另一個增長私有方法和變量以致於外部類不會看到它們的方式。這裏,你申明瞭一個數組NSMutableArry 來保存專輯數據。這個數組是可變的方便你增長和刪除專輯。
如今在PersistencyManager.m文件中@implementation行以後增長以下代碼:
- (id)init
{
self = [super init];
if (self) {
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文件中增長下面的三個方法:
- (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];
}