Translate

bloggerads內文

2017年4月13日 星期四

IOS設計模式之一(MVC模式,單例模式)

很多人在寫App一段時間時,會想針對自己的code 設計更好的寫法,因此 design-patterns就變得很重要了,以下是自己在學習 design-patterns時,所看到的文章,因此將它分享出來,其實看原文比較好,但還是將它翻出來.

IOS設計模式之一(MVC模式,單例模式)


iOS設計模式 - 你可能已經聽說過這個詞,但是你真正理解它意味著什麼嗎?雖然大多數的開發者可能都會認為設計模式是非常重要的,然而關於設計模式這一主題的文章卻不多,並且有時候我們開發者在寫程式碼的時候也不會太關注它。



在軟件設計領域,設計模式是對通用問題的可複用的解決方案。設計模式是一系列幫你寫出更可理解和復用程式碼的模板,設計模式幫你創建松耦合的程式碼以便你不需要費多大力就可以改變或者替換程式碼中的組件。
如果你剛接觸設計模式,我們有好消息告訴你!首先,多虧了Cocoa的構造方式,你已經使用了許多的設計模式以及被鼓勵的最佳實踐。
其次本指南將帶你使用絕大多數(並不是所有)Cocoa中頻繁使用的IOS設計模式。
本指南被分為了許多部分,每個部分涉及一個設計模式。在每個部分中,你將會了解到如下內容:
•設計模式是什麼?
•你為什麼要用設計模式?
•如何使用設計模式,以及在使用的時候,哪裡是合適的,哪裡是需要注意的坑。
在本指南中,你將創建一個音樂庫應用,這個應用將顯示你的專輯以及相關聯的信息。
在開發本應用的過程中,你將熟悉被大量使用的可可設計模式:
•創建型:單利(單態)和抽象工廠
•結構型:模型 - 視圖 - 控制器,裝飾器,適配器,外觀(門面)和組合模式
•行為型:觀察者,備忘錄,責任鍊和命令模式
不要被誤導認為這是一篇關於設計模式理論的文章,在本音樂應用中,你將使用這些設計模式中的大多數,最終你的音樂應用將長的像下圖所示的那樣:



我們開始吧!

下載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 碼 
  1. @property (nonatomic, copy, readonly) NSString *title, *artist, *genre, *coverUrl, *year;    
  2. - (id)initWithTitle:(NSString*)title artist:(NSString*)artist coverUrl:(NSString*)coverUrl year:(NSString*)year;  



注意到新增程式碼中所有的屬性都是只讀的,因為在專輯對象創建以後,不需要修改的值。
新增的方法是對像初始化器(object initializer),當你創建一個新的專輯(專輯)對象的時候,你需要傳遞專輯(專輯)名,藝術家,專輯封面URL,以及年份。
現在打開Album.m文件,在@implementation和@end之間增加如下程式碼:
Objective-c  
  1. - (id)initWithTitle:(NSString*)title artist:(NSString*)artist coverUrl:(NSString*)coverUrl  
  2.   year:(NSString*)year {  
  3.     self = [super init];  
  4.     if (self)  
  5.     {  
  6.         _title = title;  
  7.         _artist = artist;  
  8.         _coverUrl = coverUrl;  
  9.         _year = year;  
  10.         _genre = @"Pop";  
  11.     }  
  12.     return self;  
  13. }  


這裡沒什麼複雜花哨的東西,僅僅是一個創建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   
  1. - (id)initWithFrame:(CGRect)frame   albumCover:(NSString*)albumCover;  
現在打開AlbumView.m,用如下程式碼替換@implementation 之後所有的程式碼
Objective-c   
  1. @implementationAlbumView  
  2. {  
  3.     UIImageView *coverImage;  
  4.     UIActivityIndicatorView *indicator;  
  5. }  
  6.    
  7. - (id)initWithFrame:(CGRect)frame albumCover:(NSString*)albumCover  
  8. {  
  9.     self = [super initWithFrame:frame];  
  10.     if (self)  
  11.     {  
  12.    
  13.         self.backgroundColor = [UIColor blackColor];  
  14.         // the coverImage has a 5 pixels margin from its frame  
  15.         coverImage = [[UIImageView alloc] initWithFrame:CGRectMake(55, frame.size.width-10,  
  16.    
  17.         frame.size.height-10)];  
  18.         [self addSubview:coverImage];  
  19.    
  20.         indicator = [[UIActivityIndicatorView alloc] init];  
  21.         indicator.center = self.center;  
  22.         indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;  
  23.         [indicator startAnimating];  
  24.         [self addSubview:indicator];  
  25.     }  
  26.     return self;  
  27. }  
  28.    
  29. @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   
  1. @interfaceLibraryAPI : NSObject  
  2.    
  3. + (LibraryAPI*)sharedInstance;  
  4.    
  5. @end  


現在打開LibraryAPI.m,在@implementation 那一行後面插入下面的方法:
Objective-c   
  1. + (LibraryAPI*)sharedInstance  
  2. {  
  3.     // 1  
  4.     static LibraryAPI *_sharedInstance = nil;  
  5.    
  6.     // 2  
  7.     static dispatch_once_t oncePredicate;  
  8.    
  9.     // 3  
  10.     dispatch_once(&oncePredicate, ^{  
  11.         _sharedInstance = [[LibraryAPI alloc] init];  
  12.     });  
  13.     return _sharedInstance;  
  14. }  


在這個簡短的方法中,有一些需要需要注意的點:
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   
  1. - (NSArray*)getAlbums;  
  2. - (void)addAlbum:(Album*)album atIndex:(int)index;  
  3. - (void)deleteAlbumAtIndex:(int)index;  



上面是你需要處理專輯數據的方法的原型。
打開PersistencyManager.m文件,在@implementation行之前,增加下面的程式碼:
Objective-c   
  1. @interfacePersistencyManager () {  
  2.     // an array of all albums  
  3.     NSMutableArray *albums;  
  4. }  


上面增加了一個類擴張(class extension),這是另外一個增加私有方法和變數以至於外部類不會看到方式。這裡,你申明了一個數組NSMutableArry 來儲存專輯數據。這個數組是可變的方便你增加和刪除專輯。
現在在PersistencyManager.m文件中@implementation行之後增加如下程式碼:
Objective-c 碼   
  1. - (id)init  
  2. {  
  3.     self = [super init];  
  4.     if (self) {  
  5.         // a dummy list of albums  
  6.         albums = [NSMutableArrayarrayWithArray:  
  7.                  @[[[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"],  
  8.                  [[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"],  
  9.                  [[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"],  
  10.                  [[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"],  
  11.                  [[Album alloc] initWithTitle:@"American Pie" artist:@"Madonna" coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_madonna_american%20pie.png" year:@"2000"]]];  
  12.     }  
  13.     return self;  
  14. }  


在init中,你用五條樣例專輯填充數組。如果你不喜歡上面的專輯,你可以自由用你喜歡的專輯替換。
現在在PersistencyManager.m文件中增加下面的三個方法:
Objective-c   
  1. - (NSArray*)getAlbums  
  2. {  
  3.     return albums;  
  4. }  
  5.    
  6. - (void)addAlbum:(Album*)album atIndex:(int)index  
  7. {  
  8.     if (albums.count >= index)  
  9.         [albums insertObject:album atIndex:index];  
  10.     else  
  11.         [albums addObject:album];  
  12. }  
  13.    
  14. - (void)deleteAlbumAtIndex:(int)index  
  15. {  
  16.     [albums removeObjectAtIndex:index];  
  17. }  


這些方法讓你可以增加和刪除專輯。
構建你的工程確保每個資源都可以被正確的編譯。
這時候,你可能想知道PersistencyManager類來自哪裡?因為它不是一個單例類。下一部分,我們將探究LibraryAPI 和PersistencyManager之間的關係,那時候你將看到門面或者外觀(Facade)模式。



沒有留言:

張貼留言