国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

iOS開發(fā)日記38-MVVM與ReactiveCocoa

2019-11-14 18:03:59
字體:
供稿:網(wǎng)友

今天博主有一個(gè)MVVM與ReactiveCocoa的需求,遇到了一些困難點(diǎn),在此和大家分享,希望能夠共同進(jìn)步.

Apple本身的UIKit框架是為MVC模式設(shè)計(jì)的,所以你在無形之中寫就的代碼其實(shí)就是MVC,而且你甚至?xí)X得代碼就應(yīng)該這么寫,不這么寫還能怎么寫?!MVVM由于缺乏框架級別的支持,所以在iOS的開發(fā)中一直似乎是很雞肋式的存在.直到出現(xiàn)了 ReactiveCocoa !

它從框架界別支持MVVM模式,它讓你真切地感覺到自己以前的代碼真的太亂了,它也讓你真正有興趣去嘗試下一些比較流行的編程模式,比如響應(yīng)式,函數(shù)式,MVVM等.出于自己的實(shí)際項(xiàng)目需要,必須最低支持 iOS 7版本,所以在進(jìn)行本文之前,先對 RAC(ReactiveCocoa的簡稱,后文同)作了一番研究.

雖然官方文檔指明 3.0版本的RAC,最低支持的 是iOS 8.0,但是我們依然可以通過 CocoaPods 安裝 2.5版本的ReactiveCocoa來在自己的項(xiàng)目中使用,具體細(xì)節(jié)參見:  ReactiveCocoa,最受歡迎的iOS函數(shù)響應(yīng)式編程庫(2.5版),沒有之一!

基本概念篇: MVC VS MVVM

MVC

提到MVC,你現(xiàn)在可以先自己回想一下自己寫過的程序,然后再往下看.

  • M 指的是Model,數(shù)據(jù)模型,它可以是一個(gè)系統(tǒng)自身的類型,比如字符串,數(shù)組等,也可以是一個(gè)自定義的類型. 以上篇文章為例,你可以認(rèn)為 YFMVCPostListViewController 的 categoryName 屬性是一個(gè)Model,也可以認(rèn)為 articles 屬性是Model.Model 就是那個(gè)用來存儲(chǔ)數(shù)據(jù)的東西. 
  • V,指的是View,通俗點(diǎn)說,所有UIView及其子類都屬于V部分. 
  • C,指的就是UIViewController 及其子類.

所以說, UIKit自身就是為MVC模式設(shè)計(jì)的,而你就算不清除什么是MVC,但你的代碼其實(shí)就是MVC模式.當(dāng)你閱讀自己以前的代碼或者別人的代碼時(shí),經(jīng)常感覺這個(gè)代碼寫的好亂(shi)啊,其實(shí)這真的不是自己或別人的鍋,這是MVC本身難以避免甚至必然會(huì)出現(xiàn)的一個(gè)坑!所以,后來有人借鑒其他語言,提出了MVVM模式,并躬身實(shí)踐!

MVVM

首先,MVVM,從概念說上來說,真的很好,很吸引人,即使你可能看不太懂,也感覺很高大上的樣子!但是,當(dāng)你真的去百度相關(guān)概念時(shí),往往會(huì)很納悶,似乎比我現(xiàn)在還麻煩,甚至開始懷疑,MVVM應(yīng)該還只停留在理論階段吧!

--NO,只是因?yàn)槟銢]有找到合適的文章,沒有找到合適的工具--ReactiveCocoa!還是先說一下 MVVM的基礎(chǔ)概念吧,不然沒法往下說了:

  • 第一個(gè)M,和MVC中的M基本一樣.但是要求更輕量級.MVC中的M,你可以會(huì)放一些和原始數(shù)據(jù)不相關(guān)的推斷出來的屬性或者工具方法,如Person類,你可能給他寫一個(gè)方法來根據(jù)原始數(shù)據(jù)年齡來判斷是否有資格做某事,比如結(jié)婚;但是MVVM中的M,根據(jù)我的理解,你直接用它來存放元數(shù)據(jù)(這里,可能還是有爭議的,僅是個(gè)人的理解與實(shí)踐). 
  • 第一個(gè)V,比MVC中的V要更廣泛些,它包括 UIView 與 UIViewController及其子類,View用來顯示和交互,UIViewController擔(dān)當(dāng)一種類似于橋梁的角色,來使 View 和 ViewModel部分更好通信. 
  • 余下的"VM",其實(shí)是一個(gè)整體,指的是ViewModel,視圖數(shù)據(jù)模型.如果你以前的許多代碼都放在Model中,比如沒有數(shù)據(jù)自動(dòng)聯(lián)網(wǎng)請求相關(guān)的數(shù)據(jù)什么的話,那你的那個(gè)Model其實(shí)和這個(gè)ViewModel有些像.MVVM中,要求Model更薄,最好只存儲(chǔ)原始數(shù)據(jù)信息;而對于其他的設(shè)計(jì)到邏輯的代碼,建議都放到ViewModel中.你可能會(huì)說,這樣ViewModel 會(huì)不會(huì)很亂呢?未必!ViewModel中的代碼會(huì)很多,但是ViewModel的可復(fù)用性和靈活性要遠(yuǎn)遠(yuǎn)大于ViewController.更具體點(diǎn)說,以前的一個(gè)控制器里面的代碼,現(xiàn)在可能會(huì)被拆分到1個(gè)甚至多個(gè)ViewModel中,而且你的ViewModel不僅這個(gè)控制器可以用,其他的控制器也可以用.雖然從單個(gè)控制器的邏輯代碼量來看,優(yōu)化不是很顯著,但是ViewModel的模塊化特性,將在涉及到頁面復(fù)用以及后期維護(hù)時(shí),讓人感覺心曠神怡!

關(guān)于MVVM,網(wǎng)上還有一種觀點(diǎn)是,其實(shí)可以不要Model層,直接使用ViewModel層來存儲(chǔ)數(shù)據(jù).

個(gè)人感覺,如果考慮到單元測試,此時(shí)如果有單獨(dú)的Model部分,可以根據(jù)一個(gè)Model,直接測試ViewModel的邏輯,是極好的,所以目前還是繼續(xù)保留Model部分.

另外,也是考慮到后期可能會(huì)設(shè)計(jì)到Model本身的變更,比如將Model由一個(gè)普通的NSObjet變?yōu)镃oreData的一個(gè)實(shí)體,可以很容易地讓代碼支持本地化.

此時(shí),我還在考慮的一點(diǎn)是,公司代碼其實(shí)Model部分不是由我負(fù)責(zé)的,如果想繼續(xù)引入MVVM改造項(xiàng)目,保留一個(gè)ViewModel層,也可以使我的代碼對其他項(xiàng)目成員的影響降到最低.想來也是極好的!

變革: 從MVC到MVVM

接下來,會(huì)以第一篇文章的示例為基礎(chǔ),將逐步改造為MVVM模式.

為View寫的數(shù)據(jù)模型: Model --> Model + ViewModel

我的觀點(diǎn)是,盡量不要使用系統(tǒng)自帶的數(shù)據(jù)類型,比如數(shù)組,字典等作為Model,要盡可能地使用自定義地類.

使用自定義的類,方便后期維護(hù),也可以避免一些基礎(chǔ)錯(cuò)誤,如:自定義的類,如果屬性不匹配會(huì)編譯失敗,但是如果使用字典類型,key不匹配時(shí),是不會(huì)有任何提示的(用過字典的童鞋,都懂我意思的吧).所以我們此處要:

  • 新增Model: YFCategoryArticleListModel,表示按分類分組的文章列表,其中有兩個(gè)字段:category,分類;articles,此分類下的文章列表. 
  • 新增ViewModel: YFBlogDetailViewModel 表示文章的視圖模型;YFBlogListViewModel 表示 分類文章列表的視圖模型; YFBlogListItemViewModel 表示文章列表單個(gè)單元格的視圖模型;

Model僅用于存儲(chǔ)數(shù)據(jù),ViewModel的具體邏輯下面需要時(shí),會(huì)具體分析.另外,必須提到一點(diǎn)的是@青玉伏案,給我推薦了一個(gè)RAC的VM框架  ReactiveViewModel ,有興趣的可以研究下.但是我不是很能理解這么做的必要性,所以暫時(shí)我還是按照我自己的理解,用最常規(guī)的方式來寫ViewModel部分. 

使用ViewModel作為模塊入口: M + C --> VM + C

就像我開篇序言中提到的那樣,MVVM系列的文章,不單單是關(guān)于MVVM的討論,更是關(guān)于如何將已有MVC項(xiàng)目逐步過渡為MVVM架構(gòu)的可行性以及方法步驟的探究.

這里我采用的是一種折中的更具可行性的方案: 我對外暴露的接口是ViewModel,但是對應(yīng)的會(huì)給這個(gè)ViewModel提供一個(gè)使用Model作為參數(shù)的便利初始化方法;控制器或模塊內(nèi)部,就直接使用傳入的ViewModel.

這樣,我覺得才是極好的,一方面自己可以踐行MVVM,提前踩踩坑,另一方面也基本不會(huì)對其他小伙伴的開發(fā)工作造成太多的困擾!具體到本文示例,具體指:

  • 文章列表控制器: 為了與MVC模式區(qū)分,新建控制器YFMVVMPostListViewController,并添加夠公有屬性viewModel,它是YFCategoryArticleListViewModel 類型. 
  • 文章詳情控制器: 為了與MVC模式區(qū)分,新建控制器YF 
  • MVVMPostListViewController,僅添加只讀屬性viewModel,它是YFArticleViewModel類型.

關(guān)于ViewModel的自定義下面會(huì)具體談到.

實(shí)現(xiàn)ViewModel.

必須指出的一點(diǎn)是: ViewModel是為View服務(wù)的,它的命名和字段定義應(yīng)該根據(jù)View的需要來進(jìn)行.本例是一個(gè)非常簡單的場景.在復(fù)雜的場景中,一個(gè)model可能對應(yīng)多個(gè)viewModel,此時(shí)多個(gè)視圖可能都是同一種數(shù)據(jù)的不同展示方式;一個(gè)viewModel可能對應(yīng)多個(gè)model,此時(shí)頁面比較復(fù)雜,設(shè)計(jì)到多種數(shù)據(jù)的展示.簡言之,應(yīng)該是一個(gè)View對應(yīng)一個(gè)ViewModel(這一點(diǎn),可能也有待商榷,但暫時(shí)我會(huì)采取此種方式).所以,你的ViewModel中的屬性不必和某個(gè)Model有真正意義上的對應(yīng)關(guān)系,而是應(yīng)該根據(jù)它服務(wù)的View來寫和命名.

YFBlogListItemViewModel 博客列表單個(gè)單元格的視圖模型

  • 添加屬性intro: 這個(gè)viewModel 供展示博客列表中的單個(gè)單元格使用,但根據(jù)目前的UI顯示,只需要一個(gè)字段即可,我們給它命名為 intro,字符串屬性.這個(gè)后期可以根據(jù)UI變化動(dòng)態(tài)更改.就像上面提到的,ViewModel是為Model服務(wù)的. 
  • 添加屬性 blogId. 
  • 添加初始化方法 -initWithArticleModel: 以便于從一個(gè)YFArticleModel對象構(gòu)建視圖模型. 
  • 注意需要在初始化時(shí)設(shè)置 introl和model的title,desc屬性的級聯(lián)關(guān)系(我喜歡這么稱呼,意會(huì),有點(diǎn)重寫getter方法的感覺).這一步本來是在Controller中完成的,現(xiàn)在挪到了 ViewModel中,Controller 不就瘦了一點(diǎn)了嗎,而且把這個(gè)邏輯寫到這里還更方便代碼復(fù)用.
- (instancetype)initWithArticleModel:(YFArticleModel *)model{  self = [super init];  if (nil != self) {    // 設(shè)置intro屬性和model的屬性的級聯(lián)關(guān)系.    RAC(self, intro) = [RACSignal combineLatest:@[RACObserve(model, title), RACObserve(model, desc)] reduce:^id(NSString * title, NSString * desc){      NSString * intro = [NSString stringWithFormat: @"標(biāo)題:%@ 內(nèi)容:%@", model.title, model.desc];      return intro;    }];    // 設(shè)置self.blogId與model.id的相互關(guān)系.    [RACObserve(model, id) subscribeNext:^(id x) {      self.blogId = x;    }];  }  return self;}

YFBlogListViewModel 博文列表的視圖模型.

  • 添加屬性blogListItemViewModels,NSArray 類型,用于存儲(chǔ)文章列表單元格的視圖模型.視圖部分檢測它的變化,然后動(dòng)態(tài)刷新視圖即可. 
  • 添加工具方法: -first 與 -next,用于支持常見的數(shù)據(jù)分頁操作,配合blogListItemViewModels,可以實(shí)現(xiàn)常見的上拉刷新與加載加載的操作. 
  • 添加初始化方法 -initWithCategoryArtilceListModel, 用于快速使用一個(gè)分類文章列表數(shù)據(jù)模型來快速初始化.再次強(qiáng)調(diào)一次: model 和 viewModel 并不是一一對應(yīng)的關(guān)系,這里只是為了簡化從一種Model生成此種ViewModel的操作;即,以后如果有其他種類的可以使用此種ViewModel的話,我們再為其添加一個(gè)從新Model初始化的方法即可. 
  • 初始化時(shí),涉及到網(wǎng)絡(luò)請求,在此處我們額外引入了一個(gè)AFN擴(kuò)展  AFNetworking-RACExtensions ,用于使用RAC的語法格式使用AFN. 
// 接口完整地址,肯定是受分類和頁面的影響的.但是因?yàn)榉诸惖淖兓罱K會(huì)通過分頁的變化來體現(xiàn),所以此處僅需監(jiān)測分頁的變化情況即可.[RACObserve(self, nextPageNumber) subscribeNext:^(NSNumber * nextPageNumber) {  NSString * path = [NSString stringWithFormat: @"http://www.ios122.com/find_php/index.php?viewController=YFPostListViewController&model[category]=%@&model[page]=%@", self.category, nextPageNumber];  self.requestPath = path;}];// 每次數(shù)據(jù)完整接口變化時(shí),必然要同步更新 blogListItemViewModels 的值.[RACObserve(self, requestPath) subscribeNext:^(NSString * path) {  /**   *  分兩種情況: 如果是變?yōu)?,說明是重置數(shù)據(jù);如果是大于0,說明是要加載更多數(shù)據(jù);不處理向上翻頁的情況.   */  NSMutableArray * articls = [NSMutableArray arrayWithCapacity: 42];  if (YES != [self.nextPageNumber isEqualToNumber: @0]) {    [articls addObjectsFromArray: self.blogListItemViewModels];  }  [[self.httpClient rac_GET:path parameters:nil] subscribeNext:^(RACTuple *JSONAndHeaders) {    // 使用MJExtension將JSON轉(zhuǎn)換為對應(yīng)的數(shù)據(jù)模型.    NSArray * newArticles = [YFArticleModel objectArrayWithKeyValuesArray: JSONAndHeaders.first];    // RAC 風(fēng)格的數(shù)組操作.    RACSequence * newblogViewModels = [newArticles.rac_sequence                map:^(YFArticleModel * model) {                  YFBlogListItemViewModel * vm = [[YFBlogListItemViewModel alloc] initWithArticleModel: model];                  return vm;                }];    [articls addObjectsFromArray: newblogViewModels.array];    self.blogListItemViewModels = articls;  }];}];

關(guān)于MVVM的優(yōu)勢,此處已可見一斑!我們成功的從控制器中剝離了網(wǎng)絡(luò)請求以及數(shù)據(jù)分頁的相關(guān)代碼.

從整體代碼量的角度,我們可能沒少寫幾行代碼;但是從代碼復(fù)用性的角度考慮,我們的代碼更具有可復(fù)用性,因?yàn)閷砜赡芷渌胤揭矔?huì)用到這個(gè)頁面;與此同時(shí),代碼之間的耦合性也降低了很多;可擴(kuò)展性大大提高[PS: 關(guān)于代碼耦合性,可復(fù)用性什么的,真的很大程度上是由模式本身決定的!]

YFBlogDetailViewModel 文章詳情頁的視圖模型.

  • 添加屬性content,用于直接在網(wǎng)頁視圖上顯示,View內(nèi)檢測這個(gè)屬性值,動(dòng)態(tài)刷新視圖即可. 
  • 添加初始化方法 -initWithModel: 用于方便從一個(gè) YFArticleModel 數(shù)據(jù)模型新建相應(yīng)的視圖模型. 
  • 設(shè)計(jì)到網(wǎng)絡(luò)請求部分的核心代碼如下:
/** *  公共的與Model無關(guān)的初始化. */- (void)setup{  // 初始化網(wǎng)絡(luò)請求相關(guān)的信息.  self.httpClient = [AFHTTPRequestOperationManager manager];  self.httpClient.requestSerializer = [AFJSONRequestSerializer serializer];  self.httpClient.responseSerializer = [AFJSONResponseSerializer serializer];  // 接口完整地址,肯定是受id影響.  [RACObserve(self, blogId) subscribeNext:^(NSString * blogId) {    NSString * path = [NSString stringWithFormat: @"http://www.ios122.com/find_php/index.php?viewController=YFPostViewController&model[id]=%@", blogId];    self.requestPath = path;  }];  // 每次完整的數(shù)據(jù)接口變化時(shí),必然要同步更新 self.content 的值.  [RACObserve(self, requestPath) subscribeNext:^(NSString * path) {    [[self.httpClient rac_GET:path parameters:nil] subscribeNext:^(RACTuple *JSONAndHeaders) {      // 使用MJExtension將JSON轉(zhuǎn)換為對應(yīng)的數(shù)據(jù)模型.      YFArticleModel * model = [YFArticleModel objectWithKeyValues:JSONAndHeaders.first];      self.content = model.body;    }];  }];}

如果耐心比較下 -setup 方法中的代碼,會(huì)發(fā)現(xiàn)與上個(gè)VM的-setup有許多共同之處,這就啟發(fā)我們,或許應(yīng)該將網(wǎng)絡(luò)請求類從VM中進(jìn)一步剝離出來,制作一個(gè)通用的網(wǎng)絡(luò)請求類.通用網(wǎng)絡(luò)請求類與單元測試的相關(guān)話題,會(huì)在下篇MVVM系列文章中專門講述,在此不再繼續(xù)討論.

不要為了RAC而RAC: 其實(shí)你可以使用你熟悉的方式寫View的.

坦白說,RAC真的讓人很喜歡;但是,我在這里想說的是, RAC 只是簡化編碼的工具而已--所謂工具,就是那種你掌握了可以走的更快,不會(huì)也無傷大雅的東西!

國內(nèi),部分文章過分渲染 RAC 與UIKit 的差異,甚至有人宣稱是另一條完全不同的學(xué)習(xí)曲線--真的很扯,邏輯上無異于就像宣稱沒有MFC,所有人都會(huì)餓死一樣!

在此,就不過多吐槽了,反正我是很早就看過國內(nèi)某些博主的關(guān)于RAC的文章,被博主忽悠忽悠的不行,最終得出的結(jié)論是,太難了,暫時(shí)不學(xué)!

如果,你剛好看到這篇文章,我想對你說的是: 耐下心,花一兩天結(jié)合自己的工程和基礎(chǔ)的RAC語法,嘗試用RAC寫寫代碼試試,真的很贊,而且是有足夠的姿勢完全兼容以前的自己寫法的!View部分,在此我就暫時(shí)不用RAC中的寫法來替代block,代理等,盡可能地在MVC的代碼上,適當(dāng)修正,以證明二者的某種程度上的協(xié)同作用.

控制器中的代碼,真的被精簡了不少,以博客列表控制器為例,幾乎占據(jù)1/2控制器代碼量的網(wǎng)絡(luò)請求與數(shù)據(jù)分頁的代碼,被簡化為一句話:

[RACObserve(self.viewModel, blogListItemViewModels) subscribeNext:^(id x) {    [self updateView];}];

同樣的,博客詳情也精簡了非常多,忍不住想曬下完整代碼:

////  YFMVVMPostViewController.m//  iOS122////  Created by 顏風(fēng) on 15/10/21.//  Copyright (c) 2015年 iOS122. All rights reserved.//#import "YFMVVMPostViewController.h"#import "YFBlogDetailViewModel.h"#import <ReactiveCocoa.h>@interface YFMVVMPostViewController ()@property (strong, nonatomic) UIWebView * webView;@end@implementation YFMVVMPostViewController- (void)viewDidLoad {  [super viewDidLoad];  // Do any additional setup after loading the view.  [RACObserve(self.viewModel, content) subscribeNext:^(id x) {    [self updateView];  }];}- (void)didReceiveMemoryWarning {  [super didReceiveMemoryWarning];  // Dispose of any resources that can be recreated.}- (UIWebView *)webView{  if (nil == _webView) {    _webView = [[UIWebView alloc] init];    [self.view addSubview: _webView];    [_webView makeConstraints:^(MASConstraintMaker *make) {      make.edges.equalTo(UIEdgeInsetsMake(0, 0, 0, 0));    }];  }  return _webView;}/** * 更新視圖. */- (void) updateView{  [self.webView loaDHTMLString: self.viewModel.content baseURL:nil];}@end

  • http://www.cocoachina.com/industry/20140126/7759.html
  • http://www.tuicool.com/articles/6RfEJf7?plg_nld=1&plg_uin=1&plg_auth=1&plg_nld=1&plg_usr=1&plg_vkey=1&plg_dev=1
  • http://www.jianshu.com/p/2cfed74789db?plg_nld=1&plg_uin=1&plg_auth=1&plg_nld=1&plg_usr=1&plg_vkey=1&plg_dev=1

http://mp.weixin.QQ.com/s?plg_nld=1&plg_uin=1&mid=400435726&idx=1&plg_nld=1&scene=23&plg_auth=1&__biz=MjM5MDE0Mjc4MA%3D%3D&plg_dev=1&srcid=11032ZYiaJix9NRGyZo1sPbj&plg_usr=1&plg_vkey=1&sn=8bcca03cd94f053dccd69b1a700cac3d#wechat_redirect&appinstall=0


發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 七台河市| 台南县| 中卫市| 武汉市| 邯郸县| 嘉峪关市| 黄山市| 樟树市| 南涧| 郴州市| 张北县| 会同县| 清徐县| 清新县| 贵德县| 通榆县| 田东县| 武功县| 博野县| 黑河市| 分宜县| 汉川市| 额敏县| 寿阳县| 调兵山市| 石台县| 分宜县| 唐海县| 精河县| 奇台县| 修武县| 太仆寺旗| 富阳市| 门源| 德阳市| 德兴市| 赤水市| 正宁县| 平阴县| 谷城县| 黑水县|