IOS設計模式之一(MVC模式,單例模式)
iOS 設計模式-你可能已經聽說過這個詞,但是你真正理解它意味著什麼嗎?雖然大多數的開發人員可能都會認為設計模式是非常重要的,然而關於設計模式這一主題的文章卻不多,並且有時候我們開發人員在寫代碼的時候也不會太關注它。
在軟體設計領域,設計模式是對通用問題的可複用的解決方案。設計模式是一系列幫你寫出更可理解和複用代碼的模板,設計模式幫你建立松耦合的代碼以便你不需要費多大力就可以改變或者替換代碼中的組件。
如果你剛接觸設計模式,我們有好訊息告訴你!首先,多虧了Cocoa的構建方式,你已經使用了許多的設計模式以及被鼓勵的最佳實務。其次本指南將帶你使用絕大多數(並不是所有)Cocoa中頻繁使用的IOS 設計模式。
本指南被分為了許多部分,每個部分涉及一個設計模式。在每個部分中,你將會瞭解到如下內容:? 設計模式是什嗎?? 你為什麼要用設計模式?? 如何使用設計模式,以及在使用的時候,哪裡是合適的,哪裡是需要注意的坑。在本指南中,你將建立一個音樂庫應用,這個應用將顯示你的專輯以及它們相關聯的資訊。在開發本應用的過程中,你將熟悉被大量使用的Cocoa 設計模式:? 建立型:單利(單態)和 抽象工廠? 結構型:模型-視圖-控制器,裝飾器,適配器,外觀(門面)和組合模式? 行為型:觀察者,備忘錄,責任鏈和命令模式不要被誤導認為這是一篇關於設計模式理論的文章,在本音樂應用中,你將使用這些設計模式中的大多數,最終你的音樂應用將長的像所示的那樣:
我們開始吧!下載 starter project,匯出zip檔案的內容,然後用xcode開啟BlueLibrary.xcodeproj.工程裡面沒有太多的檔案,僅僅包含預設的ViewController以及空實現的HTTP Client.注意:當你建立一個新的Xcode工程的時候,你的代碼其實已經涉及到了設計模式,你知道嗎?模型-視圖-控制器,委託,協議,單例-你不費吹灰之力就可以免費使用它們啦。在你深入到第一個設計模式之前,你首先必須建立兩個類,用這兩個類去儲存和顯示音樂庫專輯的資訊。在Xcode中,導航到"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模式的好的實現也就意味著每一個對象都會被劃分到上面所說的組中。 我們可以很好的用來描述通過控制器實現的視圖到模型的互動過程:
vcTjtcTXysHPv+LW0KOsxOPI1Mi7v8nS1Mq508PNrNH5tcRBbGJ1bVZpZXfIpc/Uyr6159Owus3K6byuyv2+3aGjuPy9+NK7sr3AtMu1o6zI57n7xOPP67S0vajSu7j20MK1xNPr16i8rdPQudjBqrXEuaSzzKOsxOO/ydLUuty88rWltcS4tNPDQWxidW3A4KOs0vLOqsv8srvSwMC1yM66zsrTzbyho9Xivs3Kx01WQ7XEx7+089autKahowoKPHN0cm9uZz7I57rOyrnTw01WQ8Sjyr08L3N0cm9uZz4KCsrXz8ijrMTj0OjSqsi3saPU2sTjuaSzzNbQtcTDv7j2wODKx7/Y1sbG96OsxKPQzbrNytPNvNbQtcTSu9bWo6yyu9Kq1NrSu7j2wODW0Nfpus/BvdbWvcfJq7XEuabE3KGjtb3Ev8ewzqrWuaOsxOO0tL2owcvSu7j2QWxidW3A4LrNQWxidW1WaWV3wOCjrNXi0fnX9s2musO1xKGjCgrG5LTOo6zOqsHLyLexo8TjxNy3+7rP1eLW1rmk1/e3vbeoo6zE49OmuMO0tL2oyP249rmks8zX6aOoUHJvamVjdCBHcm91cKOpwLSxo7TmxOO1xLT6wuujrMO/uPa5pLPM1+nWu7Tmt8XSu9bWwODQzbXEtPrC66GjCgq1vLq9tb0="檔案\建立\組(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塊中的代碼將不會執行(因為它已經被執行了一次),你將得到原先已經初始化好的執行個體。
注意: 為了學習