實際案例講解iOS設計模式——MVC模式

       MVC模式是iOS編程中提到的最多次的設計模式,也是使用最頻繁的設計模式之一。網絡上有很多的MVC模式的分析文章,但都是從原理上來解釋,很少能找到配套的案例來說明到底在實際的項目中要如何的使用這種模式。小編在經過詳細的研究、對比和實驗了之後,總結了一下這個模式的一些簡單使用方法,希望能起一個拋磚引玉的作用,使得對MVC默認的同學能依葫蘆畫瓢的瞭解MVC模式的使用方法,並以此類推出更多、更好的方法出來。

       這篇文章先從老生常談的MVC設計模式的原理說起,然後配上一個簡單的案例,以演示如何將一個常規的編程改爲使用MVC模式來編程。


       本文假定,您已經熟悉了target-action模式、delegate模式、Notification或KVO模式。如果這幾個模式還不是很瞭解,可參考網絡上其它文章說明,本文不會就這些模式做詳細講解。


一、什麼是MVC?


      MVC是model-view-control的簡稱。在解釋這個名詞之前,我們先假定一個案例,在這個例子中,有兩個文本框T1和T2、兩個按鈕B1和B2,B1爲保存按鈕,B2爲加載按鈕;T1爲文本內容編輯按鈕,當點擊B1時,將T1裏面的內容保存到數據庫中;當點擊B2時,將數據庫中保存的內容顯示到T2上。爲了驗證這個操作是否是經過數據庫了,我們可以做一個操作,比如將T1裏面的內容後面加上一些字符、符號等(這個相信很好做吧,你可以在將T1的數據保存到數據庫的時候做這個操作,也可以在將數據從數據庫中調出、顯示在T2上之前做這個操作,本案例不在詳細敘述)。當我們腦海裏形成好了這樣一個應用之後,下面我們來看看M、V、C到底做什麼樣的工作:


      View——顧名思義,就是存放視圖使用的。對應上面的例子,我們應該把T1、T2、B1和B2放在View上,對吧?


      Model——即模型。模型一般都有很好的可複用性,統一管理一些數據。在上面的例子中,數據庫是不是可以作爲一個模型呢?答案是肯定的。所以,我們就把數據庫的所有操作都放在Model裏面執行——包括但不限於數據庫的創建、插入、查詢、更新和刪除(爲啥都放一起?地球人都知道。。。)


     Controller——控制器,充當一個CPU的功能,即該應用程序所有的工作都由Controller統一調控。它負責處理View和Model的事件。具體怎麼調控和處理?在下面的MVC原理裏面,我們將詳細講解。


      MVC模式能夠完成各司其職的任務模式,由於降低了各個環節的耦合性,大大優化Controller的代碼量,當程序調試時,如果某一個功能沒有按照既定的模式工作,可以很方便的定位到到底是Controller還是View還是Model出了問題,而且還利於程序的可複用性,建議在程序中能多多使用這個模式。


二、MVC的原理


      上面的內容中,已經詳細描述了model、view和controller之間如何各司其職(即該是誰的東西,誰就要保護好,不能讓另外一個越俎代庖的去處理)。MVC模式雖然是iOS編程中使用最廣泛的模式,但論起復雜程度,MVC模式可以算是衆多設計模式之首。通常情況下,MVC模式需要綜合使用target-action模式、delegate模式、Notification或KVO模式等。下圖是斯坦福大學的iOS一堂關於iOS介紹的公開課上所使用的示例圖,這張圖像也生動的描繪出來了MVC模式的工作原理,接下來的原理講解也是依託於這張圖像:

1、 Controller和View之間可以通信,Controllor通過outlet(輸出口)控制View,View可以通過target-action、delegate或者data source(想想UITableVeiwDatasource)來和Controller通信;

2、 Controller在接收到View傳過來的交互事件(View就是完成讓人和程序的交互的呀,比如按B1按鈕)之後,經過一些判斷和處理,把需要Model處理的事件遞交給Model處理(比如剛纔的例子中的保存到數據庫),Controller對Model使用的是API;

3、 Model在處理完數據之後,如果有需要,會通過Notification或者KVO的方式告知Controller,事件已經處理完,Controller再經過判斷和處理之後,考慮下一步要怎麼辦(是默默無聞的在後臺操作,還是需要更新View,這得看Controller的“臉色”行事)。這裏的無線天線很有意思,Model只負責發送通知,具體誰接收這個通知並處理它,Model並不關心,這一點非常重要,是理解Notification模式的關鍵。

4、 Model和View之間不直接通信!


       按照上面的原理,我們知道了M、V、C之間的各司其職——Model不保存控件,View不做數據庫操作(但這個也不是絕對,如果需要View做一些數據緩存工作,還是需要保存一些臨時數據的),而Controller就充當了兩者之間的協調器。  

       如此,MVC的原理已經理出來一個頭緒了,那麼我們來看一個實際的例子,來驗證如何使用MVC模式。在這個例子中,View通過target-action模式向Controller傳遞消息,Controller通過API調用Model裏面的方法來處理從View那接收到的消息;Model處理完數據之後,通過Notification模式向Controller傳遞一個消息,最終Controller通過一個方法(即Notification的接收方法)彈出來一個對話框顯示Model已經處理完成。


三、實際案例


1、 不使用MVC模式的案例:


1.1 使用Xcode創建一個Single View Application,命名爲MVCsample。

1.2 在ViewController.h裏面,添加如下代碼:



[objc] view plaincopy

  1. @property (nonatomicstrongUIButton *saveBtn; //點擊該按鈕,保存數據   

  2. @property (nonatomicstrongUIButton *loadBtn; //點擊該按鈕,加載數據  





如下圖所示:


1.3 在ViewController.m文件的- (void)viewDidLoad方法中,添加如下代碼:


[objc] view plaincopy

  1. _saveBtn = [UIButton buttonWithType:UIButtonTypeCustom];  

  2.   

  3. [_saveBtn setFrame:CGRectMake(505015080)];  

  4.   

  5. [_saveBtn setTitle:@"保存" forState:UIControlStateNormal];  

  6.   

  7. [_saveBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];  //默認的頁面背景色是白色,butotn上的文字的默認顏色也是白色,所以在此處將button上的文字顏色設置爲黑色,以便顯示  

  8.   

  9.   

  10. [_saveBtn addTarget:self action:@selector(saveBtnPressed:) forControlEvents:UIControlEventTouchUpInside];  //添加target-action模式  

  11.   

  12. [self.view addSubview:_saveBtn];  

  13.   

  14.   

  15. _loadBtn = [UIButton buttonWithType:UIButtonTypeCustom];  

  16.   

  17. [_loadBtn setFrame:CGRectMake(5016015080)];  

  18.   

  19. [_loadBtn setTitle:@"加載" forState:UIControlStateNormal];  

  20.   

  21. [_loadBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];  

  22.   

  23. [_loadBtn addTarget:self action:@selector(loadBtnPressed:) forControlEvents:UIControlEventTouchUpInside];  //添加target-action模式  

  24.   

  25. [self.view addSubview:_loadBtn];  


如下圖所示:



1.4 在上面的一行代碼中,我們分別爲_saveBtn和_loadBtn添加了target-action模式,接下來在ViewController.m文件中實現這兩個action方法,代碼如下:


[objc] view plaincopy

  1. - (void)saveBtnPressed : (UIButton*)sender{  

  2.       

  3.     NSLog(@"保存");  

  4.       

  5.     NSLog(@"當前設備的型號:%@", [[UIDevice currentDevice]systemVersion]);  

  6.       

  7.     /* 

  8.     //從iOS 8開始,蘋果建議不使用UIAlertView和UIActionsheet,而是要使用UIAlertController。從iOS 9開始,UIAlertView和UIActionsheet更是已經被禁用,所以在這個案例中,雖然如下的UIAlertView方法還是可以照常運行,但我們還是遵從蘋果的建議,使用新的技術和方法比較妥當 

  9.      

  10.     UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"恭喜" message:@"保存成功" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:@"確定", nil]; 

  11.      

  12.     [alert show]; 

  13.       

  14.      */  

  15.       

  16.       

  17.     UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"恭喜"  

  18.                                                                              message:@"保存成功"  

  19.                                                                       preferredStyle:UIAlertControllerStyleAlert];  

  20.       

  21.     UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {  

  22.           

  23.         NSLog(@"點擊了取消按鈕");  

  24.           

  25.     }];  

  26.       

  27.     UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {  

  28.           

  29.         NSLog(@"點擊了確定按鈕");  

  30.           

  31.     }];  

  32.       

  33.     [alertController addAction:cancelAction];  

  34.     [alertController addAction:okAction];  

  35.       

  36.     [self presentViewController:alertController animated:YES completion:nil];  

  37.       

  38.       

  39.       

  40. }  

  41.   

  42. - (void)loadBtnPressed : (UIButton*)sender{  

  43.       

  44.     NSLog(@"加載");  

  45. }  


代碼有點長,不再截圖,各位請自行想象~~~



1.5 然後運行該程序,模擬器顯示如下圖:


點擊了“保存按鈕”後,模擬器及Xcode輸出臺顯示如下圖所示:



再點擊AlertView上的“取消”或者“確定”按鈕,Xcode輸出臺顯示如下:



以上內容,就是不使用MVC模式的情況下,我們經常做得內容。下面,我們就按照MVC的原理,來將改程序按照MVC的思想重新做一遍。


2. 使用MVC模式的案例:


2.1 使用Xcode創建一個Single View Application,命名爲MVCsampleWithMVC;


2.2 新建三個文件夾(Group),分別命名爲M、V和C;


2.3 新建一個名爲VView的類,繼承自UIView,並將UIView.h和UIView.m文件拖到V文件夾下;


2.4 新建一個名爲MModel的類,繼承自NSObject,並將MModel.h和MModelm文件拖到M文件夾下;


2.5 將ViewController.h和ViewController.m文件拖到C文件夾下。做完2.2~2.5的工作之後,Xcode工作組應該顯示如下圖所示:



2.6 按照MVC的思想,V裏面只存放界面顯示的控件,在剛纔的例子中,就是“保存”和“加載”這兩個按鈕,於是,我們把有關這兩個按鈕的代碼都寫到VView.h和VView.m中。

寫好後的VView.h的代碼如下:


[objc] view plaincopy

  1. #import <UIKit/UIKit.h>  

  2.   

  3. @interface VView : UIView  

  4.   

  5. @property (nonatomicstrongUIButton *saveBtn;  //點擊該按鈕,保存數據  

  6.   

  7. @property (nonatomicstrongUIButton *loadBtn;  //點擊該按鈕,加載數據  

  8.   

  9. - (void)viewInit;  //添加一個方法,用於初始化控件  

  10.   

  11. @end  


寫好後的VView.m的代碼如下:



[objc] view plaincopy

  1. #import "VView.h"  

  2.   

  3. @implementation VView  

  4.   

  5. - (void)viewInit {  

  6.       

  7.     _saveBtn = [UIButton buttonWithType:UIButtonTypeCustom];  

  8.       

  9.     [_saveBtn setFrame:CGRectMake(505015080)];  

  10.       

  11.     [_saveBtn setTitle:@"保存" forState:UIControlStateNormal];  

  12.       

  13.     [_saveBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];  //默認的頁面背景色是白色,butotn上的文字的默認顏色也是白色,所以在此處將button上的文字顏色設置爲黑色,以便顯示  

  14.       

  15.     [self addSubview:_saveBtn];  

  16.       

  17.       

  18.     _loadBtn = [UIButton buttonWithType:UIButtonTypeCustom];  

  19.       

  20.     [_loadBtn setFrame:CGRectMake(5016015080)];  

  21.       

  22.     [_loadBtn setTitle:@"加載" forState:UIControlStateNormal];  

  23.       

  24.     [_loadBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];  

  25.       

  26.     [self addSubview:_loadBtn];  

  27.       

  28. }  

  29.   

  30. @end  


和上面的不使用MVC模式例子相比較,發現_saveBtn和_loadBtn都沒有添加target-action方法。這是因爲target-action方法需要設置一個target對象,在這個對象裏調用action方法。按照MVC的思想,V裏面不進行數據處理,而是要在C裏面統一調控有C還是M來處理數據。本例中,我們是在C裏面處理,所以我會把targe-action方法寫到C裏面,詳細後面介紹。


2.7 按照MVC的思想,M主要用於數據的處理,我們假設這個案例是要將V中的某一段內容保存到數據庫中,那麼這個保存到數據庫中的操作就是在M裏面完成的。此處我們直接簡化操作,只是寫兩個方法,用來提示保存和加載成功的。如此,MModel.h中的代碼如下:


[objc] view plaincopy

  1. #import <Foundation/Foundation.h>  

  2.   

  3. @interface MModel : NSObject  

  4.   

  5. - (void)save;  

  6.   

  7. - (void)load;  

  8.   

  9. @end  


MModel.m中的代碼如下:



[objc] view plaincopy

  1. #import "MModel.h"  

  2.   

  3. @implementation MModel  

  4.   

  5. - (void)save{  

  6.       

  7.     NSLog(@"保存。。。");  

  8.       

  9.     [[NSNotificationCenter defaultCenter] postNotificationName:@"saveSucessful" object:self];  //使用Notification模式發送一個通知,用於通知Controller要做什麼事情  

  10.       

  11. }  

  12.   

  13. - (void)load{  

  14.       

  15.     NSLog(@"加載。。。");  

  16.       

  17. }  

  18.   

  19. @end  


可以看到,這段代碼和不使用MVC模式的代碼中的target-action方法中的action方法是基本上一樣的。那麼我麼就會有一個思路——在Controller中,當我們爲按鈕添加了target-action模式之後,對應要實現的action方法裏面,是不是只需要調用MModel.h裏面的對應的- (void)save和- (void)load方法就行了呢?完全正確!看,這就是C通過API調用M!



在- (void)save方法中,我還使用Notification模式發送了一個通知,這個通知用來告知Controller,我已經保存好數據了,接下來你看着辦!在上一個不使用MVC模式的例子中,Controller是彈出來一個Alert,本例子中,我們也要實現這個功能。


2.8 V和M都已經分配好了,接下來就是看C如何協調分配了。ViewController.h中的代碼如下:


[objc] view plaincopy

  1. #import <UIKit/UIKit.h>  

  2.   

  3. #import "VView.h"  

  4.   

  5. #import "MModel.h"  

  6.   

  7. @interface ViewController : UIViewController  

  8.   

  9.   

  10. @property (nonatomicstrongVView *aView;  //實例化一個VView的對象  

  11.   

  12. @property (nonatomicstrongMModel *mModel;  //實例化一個MModel的對象,以便於調用MModel中的方法  

  13.   

  14.   

  15. @end  


由於我在MModel.h中定義的方法都是實例方法,所以我們只能實例化一個MModel的對象來調用這些方法。如果將MModel中的方法設置爲類方法或者單例模式,就可以直接用MModel這個類來調用了。


2.9 ViewContro.m中的代碼如下:


[objc] view plaincopy

  1. #import "ViewController.h"  

  2.   

  3. #define deviceScreenWidth [[UIScreen mainScreen]bounds].size.width  

  4.   

  5. #define deviceScreenHeight [[UIScreen mainScreen]bounds].size.height  

  6.   

  7. @interface ViewController ()  

  8.   

  9. @end  

  10.   

  11. @implementation ViewController  

  12.   

  13. - (void)viewDidLoad {  

  14.     [super viewDidLoad];  

  15.       

  16.       

  17.     [[NSNotificationCenter defaultCenter] addObserver:self  

  18.                                              selector:@selector(saveOK:)  

  19.                                                  name:@"saveSucessful" object:nil];  //添加一個通知方法,當這個Controller接收到一個名稱爲@"saveSucessful"的通知後,就執行saveOK:方法  

  20.       

  21.       

  22.     _aView = [[VView alloc]initWithFrame:CGRectMake(00, deviceScreenWidth, deviceScreenHeight)];  //初始化時一定要設置frame,否則VView上的兩個按鈕將無法被點擊  

  23.       

  24.     [_aView viewInit];  

  25.       

  26.     [_aView.saveBtn addTarget:self action:@selector(saveBtnPressed:) forControlEvents:UIControlEventTouchUpInside];  //爲“保存”按鈕添加target-action模式  

  27.       

  28.     [_aView.loadBtn addTarget:self action:@selector(loadBtnPressed:) forControlEvents:UIControlEventTouchUpInside];  //爲“加載”按鈕添加target-action模式  

  29.       

  30.     [self.view addSubview:_aView];  

  31.       

  32.     _mModel = [[MModel alloc]init];  

  33.       

  34.       

  35. }  

  36.   

  37.   

  38. - (void)saveBtnPressed : (UIButton*)sender{  

  39.       

  40.     [_mModel save];  //調用MModel.h中的方法(API)  

  41. }  

  42.   

  43. - (void)loadBtnPressed : (UIButton*)sender{  

  44.       

  45.     [_mModel load];  //調用MModel.h中的方法(API)  

  46. }  

  47.   

  48.   

  49. - (void)saveOK : (NSNotification*) notification{  

  50.       

  51.     UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"恭喜"  

  52.                                                                              message:@"保存成功"  

  53.                                                                       preferredStyle:UIAlertControllerStyleAlert];  

  54.       

  55.     UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {  

  56.           

  57.         NSLog(@"點擊了取消按鈕");  

  58.           

  59.     }];  

  60.       

  61.     UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {  

  62.           

  63.         NSLog(@"點擊了確定按鈕");  

  64.           

  65.     }];  

  66.       

  67.     [alertController addAction:cancelAction];  

  68.     [alertController addAction:okAction];  

  69.       

  70.     [self presentViewController:alertController animated:YES completion:nil];  

  71.       

  72.       

  73. }  

  74.   

  75. - (void)didReceiveMemoryWarning {  

  76.     [super didReceiveMemoryWarning];  

  77.     // Dispose of any resources that can be recreated.  

  78. }  

  79.   

  80. @end  


代碼裏面有一些注意事項,我都用註釋的形式寫在了代碼的後面,請讀者自行研究判斷,此處不再贅述,有疑問歡迎留言討論。從C的代碼中可以看到,如何在MVC中使用target-action模式(delegate模式和data source模式,暫時不在這個案例中講述),如何在C中調用M中的API,以及M如何通過Notification模式向C發送通知並由C處理相關的通知。雖然M+V+C裏面的代碼總量比不使用MVC模式多了一些,但MVC模式寫出來的代碼層次分明,結構清楚,分工明確,爲以後修改代碼、調試程序都帶來了極大的便利。比如你要修改顯示的效果,只需要修改V中的就行,然後按照調理在C中添加相應的方法,多麼明確。使用MVC模式的運行效果我就不再附圖了,親測程序能正常工作,請讀者自己也試一試吧,手動寫寫代碼,對理解代碼會有很大的幫助的。


四、結語


       至此,MVC模式的介紹就算結束了。可能很多讀者對這麼一個小程序就使用MVC模式覺得有點得不償失,但萬丈高樓平地起,只有從小處開始就能注意細節方面的事情,對以後寫大型程序來講,會非常的有幫助的。在編程界,一直流傳有一個觀點——一週不寫代碼,再寫時就會覺得手生。其實,很多代碼或者編程習慣,並不是靠腦袋記住的,而是靠習慣養成的。只有習慣了使用良好的設計模式,才能對設計模式的使用“聊熟於胸”,才能“信手拈來”。我也相信,MVC模式絕對不止這麼一點點的思想和方法,希望有興趣的讀者朋友,能多多深入研究,多多與人探討,將MVC的模式深挖掘,挖出它的靈魂。

       本文如有紕漏,歡迎各位同仁指出並給出修改建議,爲後來者提供更加準確的諮詢。


——————————————————————————————————

歡迎關注我的微博:http://weibo.com/u/5750370715/home?wvr=5&c=spr_web_360_hao360_weibo_t001

歡迎關注我的微信公衆號,您的支持,我的榮幸。微信搜索“登頂望峯”或者掃描如下圖像二維碼即可關注。我將會在公衆號中不定時推送開發相關的技巧、諮詢、業界新聞等。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章