文章結(jié)構(gòu)
1.MVX架構(gòu)問題
1.1理解Model層
1.2 萬惡的ViewController
1.3 View的復(fù)用性
2.什么是MVVM
2.1MVVM各層的職責(zé)
修改記錄
- 將RWTFlickrSearch工程model層業(yè)務(wù)邏輯實(shí)例方法實(shí)現(xiàn)方式替換成類方法實(shí)現(xiàn)喧务。
一务豺、MVX架構(gòu)問題
1.1理解Model層:
M層要完成對(duì)業(yè)務(wù)邏輯實(shí)現(xiàn)的封裝嫉称,一般業(yè)務(wù)邏輯最多的是涉及到客戶端和服務(wù)器之間的業(yè)務(wù)交互睹逃。M層里面要完成服務(wù)器之間交互以及本地緩存和數(shù)據(jù)庫存儲(chǔ)(COREDATA, SQLITE,其他)等所有業(yè)務(wù)實(shí)現(xiàn)的封裝,并向外提供的成員方法供其他層使用缓苛,而不是簡簡單單的數(shù)據(jù)結(jié)構(gòu)。
那么這層業(yè)務(wù)邏輯實(shí)現(xiàn)的方法應(yīng)該放在哪瘟判?
目前看到的有兩種做法:
- DataMannage類集合所有當(dāng)前頁Model層數(shù)據(jù)請(qǐng)求
- 下面工程中所使用的協(xié)議法屑那,定義相關(guān)的接口函數(shù),在相應(yīng)的類中方法量没,方便進(jìn)一步細(xì)化玉转。
- 直接寫在Model類中
|-- 屬性
|-- 類方法
第三種方式目前沒有看到有工程這么使用,建議還是使用上面兩種方式殴蹄。
方式一的工程文件結(jié)構(gòu):
看下APIMannager的具體實(shí)現(xiàn):
工程地址:
MVX架構(gòu)DEMO
APIMannager.h
typedef void(^NetworkCompletionHandler)(NSError *error, id result);
typedef enum : NSUInteger {
NetworkErrorNoData,
NetworkErrorNoMoreData
} NetworkError;
@interface UserAPIManager : NSObject
- (void)fetchUserInfoWithUserId:(NSUInteger)userId completionHandler:(NetworkCompletionHandler)completionHandler;
- (void)refreshUserDraftsWithUserId:(NSUInteger)userId completionHandler:(NetworkCompletionHandler)completionHandler;
- (void)loadModeUserDraftsWithUserId:(NSUInteger)userId completionHandler:(NetworkCompletionHandler)completionHandler;
- (void)deleteDraftWithDraftId:(NSUInteger)draftId completionHandler:(NetworkCompletionHandler)completionHandler;
- (void)refreshUserBlogsWithUserId:(NSUInteger)userId completionHandler:(NetworkCompletionHandler)completionHandler;
- (void)loadModeUserBlogsWithUserId:(NSUInteger)userId completionHandler:(NetworkCompletionHandler)completionHandler;
- (void)likeBlogWithBlogId:(NSUInteger)blogId completionHandler:(NetworkCompletionHandler)completionHandler;
原工程作者這里是把整個(gè)app的網(wǎng)羅請(qǐng)求都放在一個(gè)類里究抓,不建議這么做猾担,工程越大這里的方法便會(huì)越多,可以再細(xì)分細(xì)分成某個(gè)模塊或者某個(gè)界面的數(shù)據(jù)邏輯刺下,而不是整個(gè)app所有的數(shù)據(jù)請(qǐng)求比如:PageDetailAPIMannager绑嘹。
第二種方式下面結(jié)合具體工程我們?cè)谥v。
1.2 萬惡的ViewController:
1.在MVC架構(gòu)中橘茉,因?yàn)閂iewController 類中包含了self.view 導(dǎo)致很多新手會(huì)把View的初始化布局和C的邏輯都寫在ViewController中工腋,View層和C層劃分不明確。
2.因?yàn)閁IKIt 框架的限制畅卓,頁面跳轉(zhuǎn)時(shí)不得不依賴viewController擅腰。
1.3 View的復(fù)用性
我們經(jīng)常在工程中看到類似的代碼:
//TGHomeMessageModel.h
#import <UIKit/UIKit.h>
#import "TGHomeMessageModel.h"
@interface TGMessageCell : UITableViewCell
@property(nonatomic,strong)TGHomeMessageModel *cellModel;
@end
//TGHomeMessageModel.m
-(void)setCellModel:(TGHomeMessageModel *)cellModel{
_cellModel = cellModel;
_lableTitle.text = cellModel.title;
_lableSubtitle.text = cellModel.message;
_lableDate.text = cellModel.createTimeStr;
BOOL isReadState = [cellModel.isRead boolValue];
_bageView.hidden = isReadState;
}
因?yàn)閂iew與Model的耦合導(dǎo)致View的復(fù)用時(shí)候不得不重新抽離出基類view,再繼承實(shí)現(xiàn)不同的賦值邏輯。復(fù)用性降低翁潘。
建議將View中的控件賦值剝離出來趁冈,來提高View的服用性。
目前看到兩種做法:
1.用一個(gè)專門的viewHelper類實(shí)現(xiàn)model與view的賦值拜马。
2.使用抽象類定義 binddata方法,再在具體view實(shí)現(xiàn)binddata方法渗勘。
兩種方法原理類似,都需要對(duì)View進(jìn)行一定的封裝俩莽,只是數(shù)據(jù)賦值的方式有點(diǎn)不同旺坠。
方式一:
#import "UserAPIManager.h"
@interface BlogTableViewCell : UITableViewCell
- (void)setTitle:(NSString *)title;
- (void)setSummary:(NSString *)summary;
- (void)setLikeState:(BOOL)isLiked;
- (void)setLikeCountText:(NSString *)likeCountText;
- (void)setShareCountText:(NSString *)shareCountText;
- (void)setDidLikeHandler:(void(^)())didLikeHandler;
@end
BlogCellHelper.h
#import "Blog.h"
@interface BlogCellHelper : NSObject
+ (instancetype)helperWithBlog:(Blog *)blog;
- (Blog *)blog;
- (BOOL)isLiked;
- (NSString *)blogTitleText;
- (NSString *)blogSummaryText;
- (NSString *)blogLikeCountText;
- (NSString *)blogShareCountText;
- (void)likeBlogWithBlogId:(NSUInteger)blogId completionHandler:(NetworkCompletionHandler)completionHandler;
@end
BlogTableViewController.m
#pragma mark - Utils
- (void)reloadTableViewWithBlogs:(NSArray *)blogs {
for (Blog *blog in blogs) {
[self.blogs addObject:[BlogCellHelper helperWithBlog:blog]];
}
[self.tableView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
BlogCellHelper *cellHelper = self.blogs[indexPath.row];
BlogTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ReuseIdentifier];
cell.title = cellHelper.blogTitleText;
cell.summary = cellHelper.blogSummaryText;
cell.likeState = cellHelper.isLiked;
cell.likeCountText = cellHelper.blogLikeCountText;
cell.shareCountText = cellHelper.blogShareCountText;
//點(diǎn)贊的業(yè)務(wù)邏輯
__weak typeof(cell) weakCell = cell;
[cell setDidLikeHandler:^{
if (cellHelper.blog.isLiked) {
[self.tableView showToastWithText:@"你已經(jīng)贊過它了~"];
} else {
[[UserAPIManager new] likeBlogWithBlogId:cellHelper.blog.blogId completionHandler:^(NSError *error, id result) {
if (error) {
[self.tableView showToastWithText:error.domain];
} else {
cellHelper.blog.likeCount += 1;
cellHelper.blog.isLiked = YES;
//點(diǎn)贊的業(yè)務(wù)展示
weakCell.likeState = cellHelper.blog.isLiked;
weakCell.likeCountText = cellHelper.blogTitleText;
}
}];
}
}];
return cell;
}
BlogCellHelper類處理model數(shù)據(jù),格式化后再賦值給cell視圖瞒津。
方式二:
CEReactiveView.h
#import <Foundation/Foundation.h>
@protocol CEReactiveView <NSObject>
- (void)bindViewModel:(id)viewModel;
@end
RWTSearchResultsTableViewCell.m
- (void)bindViewModel:(id)viewModel{
RWTSearchResultsItemViewModel *photo = viewModel;
self.titleLabel.text = photo.title;
[self.imageThumbnailView sd_setImageWithURL:photo.url];
[RACObserve(photo, favourites) subscribeNext:^(NSNumber* _Nullable fav) {
self.favouritesLabel.text = fav.stringValue;
self.btnFavourites.hidden = (fav == nil);
}];
[RACObserve(photo, comments) subscribeNext:^(NSNumber* _Nullable com) {
self.commentsLabel.text = com.stringValue;
self.btnComment.hidden = (com == nil);
}];
photo.isVisible = YES;
[self.rac_prepareForReuseSignal subscribeNext:^(RACUnit * _Nullable x) {
photo.isVisible = NO;
}];
}
賦值
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
RWTSearchResultsItemViewModel *cellModel = self.viewModel.searchResults[indexPath.row];
RWTSearchResultsTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"RWTSearchResultsTableViewCell"];
if(cell == nil){
cell = [[NSBundle mainBundle]loadNibNamed:@"RWTSearchResultsTableViewCell" owner:nil options:nil].firstObject;
}
[cell bindViewModel:cellModel];
return cell;
}
與上面原理一致蝉衣,同樣需要對(duì)view進(jìn)行一定的封裝括尸。好處是這樣binddata就可以在view內(nèi)部寫賦值邏輯巷蚪,減少viewController負(fù)擔(dān)。要復(fù)用時(shí)濒翻,只要在繼承類里面覆蓋binddata方法就行屁柏。
這里指對(duì)view封裝是指:一些復(fù)雜的view組件可能根據(jù)數(shù)據(jù)的type字段的不同類型,UI布局有顯示有很大的變化時(shí)有送,就需要對(duì)view布局相關(guān)的方法進(jìn)行一定的封裝淌喻,給外部調(diào)用。
這樣的方式會(huì)增加一部分工作量雀摘,但因?yàn)閯冸x了對(duì)具體數(shù)據(jù)的依賴裸删,View的可復(fù)用性大大提高。
二阵赠、什么是MVVM
先弄張圖來看下什么是MVVM:
不管是MVC涯塔、MVP肌稻、MVVM有沒有發(fā)現(xiàn)一些規(guī)律:
中心類:MVC、MVP匕荸、MVVM中心類分別是C /P/VM 爹谭。
中心類與View
不管是MVC、MVP榛搔、MVVM诺凡,View都是將用戶事件傳遞給中心類, 中心類負(fù)責(zé)View層的數(shù)據(jù)更新。
而MVVM比MVP只是多了一層數(shù)據(jù)雙向綁定践惑,View根據(jù)相應(yīng)的ViewModel自動(dòng)變化腹泌。
中心類與Model
向model層調(diào)用業(yè)務(wù)邏輯實(shí)現(xiàn)方法請(qǐng)求數(shù)據(jù),并根據(jù)需要更新model數(shù)據(jù)童本。
找到這層規(guī)律真屯,我們?cè)倏碝VVM就非常簡單明了了。
2.1 MVVM各層的職責(zé):
2.1.1 Model層
Model層各個(gè)MVX架構(gòu)都相同:
1.提供業(yè)務(wù)邏輯實(shí)現(xiàn)方法(本地存儲(chǔ)數(shù)據(jù)的交互穷娱、服務(wù)端數(shù)據(jù)的交互)
2.數(shù)據(jù)結(jié)構(gòu)
2.1.2 View層(View/ViewController)
1.負(fù)責(zé)View的初始化釋放與布局
2.響應(yīng)用戶交互事件
2.1.3 VIewModel層
- 從Model層 獲取數(shù)據(jù)
- 同步數(shù)據(jù)到Model中
- 向View層提供接口绑蔫,其中包括 數(shù)據(jù)接口(格式化的數(shù)據(jù)) 以及事件處理接口
- 通知View層數(shù)據(jù)改變。
- 業(yè)務(wù)邏輯
- 頁面跳轉(zhuǎn)
看到這些任務(wù)不少小伙伴肯定覺得似曾相識(shí)泵额,這不就是MVC架構(gòu)中C層需要負(fù)責(zé)的任務(wù)嗎配深?是的,就是因?yàn)檫@樣有人才提出MVVM實(shí)則就是把MVC架構(gòu)中的C層的任務(wù)剝離出來放在了ViewModel層實(shí)現(xiàn)嫁盲,于是從臃腫的C變成了臃腫的ViewModel篓叶,可復(fù)用性的C變成了可復(fù)用的性的ViewModel。
MVVM架構(gòu)的缺點(diǎn)以及缺點(diǎn)的改進(jìn)方法在下文我會(huì)跟大家細(xì)說羞秤。在這里這么講缸托,主要是幫助大家理解viewModel的職責(zé),方便大家實(shí)際項(xiàng)目應(yīng)用瘾蛋,大致相同與于我們之前寫的MVC架構(gòu)的C層俐镐。
2.2 結(jié)合工程我們抽幾個(gè)關(guān)鍵代碼來看下各層的實(shí)現(xiàn):
工程目錄結(jié)構(gòu)
綠色部分圈出兩個(gè)看起來有點(diǎn)奇怪的區(qū)域,等下我們?cè)僦v哺哼。先帖出幾個(gè)關(guān)鍵性的代碼:
Model層:
分兩部分組成:
- 紅色框: 業(yè)務(wù)邏輯實(shí)現(xiàn)
- 綠色框: 數(shù)據(jù)結(jié)構(gòu)
業(yè)務(wù)邏輯實(shí)現(xiàn)
業(yè)務(wù)邏輯層部分: 由協(xié)議(RWTFlickrSearch)定義業(yè)務(wù)邏輯函數(shù)接口佩抹,實(shí)現(xiàn)類(RWTFlickrSearchImpl)實(shí)現(xiàn)具體的接口函數(shù)方式,定義Model層的業(yè)務(wù)邏輯層取董。
像下面這樣:
RWTFlickrSearch協(xié)議
#import <ReactiveCocoa/ReactiveCocoa.h>
#import <Foundation/Foundation.h>
@protocol RWTFlickrSearch <NSObject>
+ (RACSignal *)flickrSearchSignal:(NSString *)searchString;
+ (RACSignal *)flickrImageMetadata:(NSString *)photoId;
@end
RWTFlickrSearchImpl
///RWTFlickrSearchImpl.h
#import <Foundation/Foundation.h>
#import "RWTFlickrSearch.h"
@interface RWTFlickrSearchImpl : NSObject<RWTFlickrSearch>
@end
///RWTFlickrSearchImpl.m
@implementation RWTFlickrSearchImpl
........
/// provides a signal that returns the result of a Flickr search
+ (RACSignal *)flickrSearchSignal:(NSString *)searchString{
return [[[RTWFlickrRequestMannage shareMannage] signalFromAPIMethod:@"flickr.photos.search"
arguments:@{@"text": searchString,
@"sort": @"interestingness-desc"}
transform:^id(NSDictionary *response) {
NSDictionary *photosDic = response[@"photos"];
NSArray *photoArray = photosDic[@"photo"];
NSNumber *totalNum = photosDic[@"total"];
RWTFlickrSearchResults *results = [RWTFlickrSearchResults new];
results.searchString = searchString;
results.totalResults = [totalNum integerValue];
NSArray *photos = [photoArray linq_select:^id(NSDictionary* jsonPhoto) {
RWTFlickrPhoto *photo = [RWTFlickrPhoto new];
photo.title = jsonPhoto[@"title"];
photo.photoID = jsonPhoto[@"id"];
photo.url = [[RTWFlickrRequestMannage shareMannage] photoSourceURLFromDictionary:jsonPhoto size:OFFlickrMediumSize];
return photo;
}];
results.photos = photos;
return results;
}]logAll];
}
+ (RACSignal *)flickrImageMetadata:(NSString *)photoId {
RACSignal *favourites = [[RTWFlickrRequestMannage shareMannage] signalFromAPIMethod:@"flickr.photos.getFavorites"
arguments:@{@"photo_id": photoId}
transform:^id(NSDictionary *response) {
NSString *total = [response valueForKeyPath:@"photo.total"];
return total;
}];
RACSignal *comments = [[RTWFlickrRequestMannage shareMannage] signalFromAPIMethod:@"flickr.photos.getInfo"
arguments:@{@"photo_id": photoId}
transform:^id(NSDictionary *response) {
NSString *total = [response valueForKeyPath:@"photo.comments._text"];
return total;
}];
return [[RACSignal combineLatest:@[favourites, comments] reduce:^id(NSString *favs, NSString *coms){
RWTFlickrPhotoMetadata *meta = [RWTFlickrPhotoMetadata new];
meta.comments = [coms integerValue];
meta.favourites = [favs integerValue];
return meta;
}] logAll];
}
這里用到了RWTFlickrSearch協(xié)議(抽象類的思想)棍苹,具體函數(shù)實(shí)現(xiàn)由一個(gè)或者多個(gè)IMPL類實(shí)現(xiàn),方便業(yè)務(wù)邏輯實(shí)現(xiàn)進(jìn)一步拆分細(xì)化茵汰。
在ViewModel層你就可以看到類似這樣的調(diào)用方式:
@interface RWTFlickrSearchViewModel ()
@property (weak, nonatomic) id<RWTViewModelServices> services;
@property NSMutableArray *mutablePreviousSearches;
@end
@implementation RWTFlickrSearchViewModel
...
- (RACSignal *)excuteSearchSignal{
[SVProgressHUD show];
return [[[RWTFlickrSearchImpl flickrSearchSignal:self.searchKey]
doNext:^(id _Nullable data) {
[SVProgressHUD dismiss];
[self.delegate respondsToSelector:@selector(searchCompleteWithResult:)] ? [self.delegate searchCompleteWithResult:data] : nil;
}]
doError:^(NSError * _Nonnull error) {
[SVProgressHUD showErrorWithStatus:error.localizedFailureReason];
}];
}
}
@end
直接調(diào)用類方法枢里,來調(diào)用具體業(yè)務(wù)邏輯實(shí)現(xiàn)方法。
數(shù)據(jù)結(jié)構(gòu)
#import <Foundation/Foundation.h>
@interface RWTFlickrPhoto : NSObject
@property (strong, nonatomic) NSString *title;
@property (strong, nonatomic) NSURL *url;
@property (strong, nonatomic) NSString *identifier;
@end
數(shù)據(jù)結(jié)構(gòu)比較簡單就不講了。
View層
和我們平時(shí)寫的MVC層沒有什么區(qū)別栏豺,貼幾個(gè)代表性的代碼出來看下梭灿。
RWTFlickrSearchViewController.m
#import "RWTFlickrSearchViewController.h"
@interface RWTFlickrSearchViewController ()<UITableViewDelegate,UITableViewDataSource>
@property (weak, nonatomic) IBOutlet UITextField *textfiledSearch;
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *loadingView;
@property (weak, nonatomic) IBOutlet UIButton *btnSearch;
@property (weak, nonatomic) IBOutlet UIButton *btnLogin;
@end
@implementation RWTFlickrSearchViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[self setUpSubviews];
[self bindData];
}
- (void)setUpSubviews{
[self.loadingView startAnimating];
}
- (void)bindData{
RAC(self,title) = RACObserve(self.viewModel, navTitle);
RAC(self.viewModel,searchKey) = self.textfiledSearch.rac_textSignal;
RAC(self.loadingView,hidden) = [self.viewModel.searchCommand.executing not];
RAC(self.textfiledSearch,textColor) = RACObserve(self.viewModel, textColor);
self.btnSearch.rac_command = self.viewModel.searchCommand;
self.btnLogin.rac_command = self.viewModel.loginCommand;
[self.viewModel.errorSignal subscribeNext:^(NSError* _Nullable error) {
NSString *msg = error.localizedFailureReason;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:msg preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleDefault handler:nil];
[alert addAction:cancelAction];
[self presentViewController:alert animated:YES completion:nil];
}];
}
View層
- view視圖布局
- view與ViewModel數(shù)據(jù)雙向綁定
- 調(diào)用ViewModel提供的用戶事件處理接口
比如這里的searchButton
的點(diǎn)擊搜索事件,與具體的ViewModel事件接口executeSearch
相綁定,viewModel內(nèi)部處理用戶點(diǎn)擊搜索的業(yè)務(wù)邏輯冰悠。
tip:
看下RACCommad的官方解釋堡妒,按鈕的點(diǎn)擊事件會(huì)觸發(fā)RACCommad任務(wù)的執(zhí)行,同時(shí)也會(huì)將當(dāng)前按鈕的使能和RACCommand的canExecute
溉卓。
觸發(fā)了事件的同時(shí)皮迟,又控制了按鈕的使能防止重復(fù)觸發(fā)一舉兩得彭谁。
ViewModel相關(guān)信息需要View層展示給用戶的泡仗,也是在數(shù)據(jù)綁定這一步實(shí)現(xiàn)。比如這里的self.viewModel.errorSignal
信號(hào)堪遂。
errorSignal信號(hào)的初始化如下:
- (void)initialize{
......
RACSignal *errors = [RACSignal merge:@[self.loginCommand.errors,self.searchCommand.errors]];
self.errorSignal = errors;
}
ViewModel層
RWTFlickrSearchViewModel.h
#import <Foundation/Foundation.h>
@class RWTFlickrSearchResults;
@protocol RWTFlickrSearchViewModelDelegate <NSObject>
- (void)searchCompleteWithResult:(__kindof RWTFlickrSearchResults* _Nullable)result;
- (void)searchNeedLogin;
@end
@interface RWTFlickrSearchViewModel : NSObject
//view數(shù)據(jù)顯示
@property (nonatomic,copy) NSString *navTitle;
@property (nonatomic,copy) NSString *searchKey;
@property (nonatomic,strong) UIColor *textColor;
//用戶事件
@property (nonatomic,strong) RACCommand *searchCommand;
@property (nonatomic,strong) RACCommand *loginCommand;
//錯(cuò)誤信息顯示
@property (nonatomic,strong) RACSignal *errorSignal;
@property (nonatomic,weak) id<RWTFlickrSearchViewModelDelegate>delegate;
@end
提供了View需要的數(shù)據(jù)接口尉尾,以及用戶事件響應(yīng)接口爆阶。
RWTFlickrSearchViewModel.m
#import "RWTFlickrSearchViewModel.h"
#import "RWTFlickrSearchImpl.h"
@interface RWTFlickrSearchViewModel()
@end
@implementation RWTFlickrSearchViewModel
- (instancetype)init{
self = [super init];
if (self) {
[self initialize];
}
return self;
}
- (void)initialize{
self.navTitle = @"search";
RACSignal *searchEnableSignal =
[[[RACObserve(self, searchKey)
map:^id _Nullable(NSString* _Nullable text) {
return @(text.length > 1);
}]
skip:1]
distinctUntilChanged];
[searchEnableSignal subscribeNext:^(NSNumber* _Nullable valid) {
UIColor *textColor = [valid boolValue] ? [UIColor blackColor] : [UIColor redColor];
self.textColor = textColor;
}];
self.searchCommand =
[[RACCommand alloc]initWithEnabled:searchEnableSignal
signalBlock:^RACSignal * _Nonnull(id _Nullable input) {
return [self excuteSearchSignal];
}];
self.loginCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
[self.delegate respondsToSelector:@selector(searchNeedLogin)] ? [self.delegate searchNeedLogin] : nil;
return [RACSignal empty];
}];
RACSignal *errors = [RACSignal merge:@[self.loginCommand.errors,self.searchCommand.errors]];
self.errorSignal = errors;
}
- (RACSignal *)excuteSearchSignal{
[SVProgressHUD show];
return [[[RWTFlickrSearchImpl flickrSearchSignal:self.searchKey]
doNext:^(id _Nullable data) {
[SVProgressHUD dismiss];
[self.delegate respondsToSelector:@selector(searchCompleteWithResult:)] ? [self.delegate searchCompleteWithResult:data] : nil;
}]
doError:^(NSError * _Nonnull error) {
[SVProgressHUD showErrorWithStatus:error.localizedFailureReason];
}];
}
因?yàn)檫@里用于搜索頁面,所以擁有沒有具體的數(shù)據(jù)Model沙咏。需要了解的小伙伴可以看下統(tǒng)一工程目錄下的RWTSearchResultsItemViewModel
#import "RWTSearchResultsItemViewModel.h"
#import "RWTFlickrPhotoMetadata.h"
#import "RWTFlickrSearchImpl.h"
@interface RWTSearchResultsItemViewModel()
@end
@implementation RWTSearchResultsItemViewModel
- (instancetype)initWithModel:(RWTFlickrPhoto *)photo{
self = [super init];
if (self) {
_photoID = photo.photoID;
_title = photo.title;
_url = photo.url;
_photo = photo;
[self initialize];
}
return self;
}
- (void)initialize{
RACSignal *visibleStateChanged = [RACObserve(self, isVisible) skip:1];
RACSignal *visibleSignal = [visibleStateChanged filter:^BOOL(NSNumber* _Nullable value) {
return [value boolValue];
}];
RACSignal *hiddenSignal = [visibleStateChanged filter:^BOOL(id _Nullable value) {
return ![value boolValue];
}];
//從隱藏狀態(tài)切換到出現(xiàn)1s后 請(qǐng)求數(shù)據(jù)
RACSignal *featchMetaData = [[visibleSignal delay:1.0f] takeUntil:hiddenSignal];
@weakify(self);
[featchMetaData subscribeNext:^(id _Nullable x) {
@strongify(self);
[[RWTFlickrSearchImpl flickrImageMetadata:self.photo.photoID] subscribeNext:^(RWTFlickrPhotoMetadata* _Nullable model) {
self.favourites = @(model.favourites);
self.comments = @(model.comments);
}];
}];
}
@end
基本上就是我上面講的實(shí)現(xiàn)下面幾個(gè)
- 從Model層 獲取數(shù)據(jù)
- 同步數(shù)據(jù)到Model中(這里沒有得到具體的體現(xiàn))
- 向View層提供接口辨图,其中包括 數(shù)據(jù)接口(格式化的數(shù)據(jù)) 以及事件處理接口
- 通知View層數(shù)據(jù)改變。
- 業(yè)務(wù)邏輯
- 頁面跳轉(zhuǎn)
頁面跳轉(zhuǎn)
這里是用coordinate的思想做的跳轉(zhuǎn)肢藐,你也可以對(duì)Vc進(jìn)行相關(guān)的弱引用再調(diào)用vc的方法跳轉(zhuǎn)或者是RWTFlickrSearch工程中作者使用的抽象類的方式)
IOS架構(gòu)之--使用Coordinator提高VC/ViewModel復(fù)用性
這一層大家有疑惑的可能主要是RAC的使用(RAC這個(gè)框架比較重故河,當(dāng)實(shí)現(xiàn)MVVM主要是用的它的數(shù)據(jù)綁定功能,這塊還是容易上手的吆豹,看兩篇文章就行)鱼的。
好了,到這里一個(gè)完整的MVVM架構(gòu)就講完了痘煤。
原工程作者的博客:
MVVM Tutorial with ReactiveCocoa
工程地址:
RWTFlickrSearch
原作者在工程中用RAC實(shí)現(xiàn)了許多巧妙的用處凑阶,可以參考下原工程RAC的使用方法。
Reactive Cocoa文章專題
Reactive Cocoa不熟的小伙伴看一下下面幾篇文章:
ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2
ReactiveCocoa Tutorial – The Definitive Introduction: Part 2/2
ReactiveCocoa進(jìn)階
ReactiveCocoa v2.5 源碼解析之架構(gòu)總覽
三衷快、MVVM問題以及改進(jìn)方式
介于MVVM ViewModel擔(dān)任了太多的任務(wù)宙橱,有人提出MVVM幾個(gè)以下的缺點(diǎn):
1.繁重的ViewModel代替了繁重的C
2.不可復(fù)用的ViewModel代替了不可復(fù)用的C
3.沒有數(shù)據(jù)綁定工具的MVVM代碼將會(huì)變得非常復(fù)雜,MVVM的實(shí)際重心在哪烦磁?如果是數(shù)據(jù)綁定那么數(shù)據(jù)綁定為什么在MVVM架構(gòu)中沒有體現(xiàn)养匈。
優(yōu)化改進(jìn)方式:
- Daniel Hall提出可以通過將viewModel按照
Data Source
,Binding
,Responder
三類功能進(jìn)一步細(xì)分哼勇,這么做可能會(huì)導(dǎo)致架構(gòu)與傳統(tǒng)的MVVM結(jié)構(gòu)工程結(jié)構(gòu)上看起來有點(diǎn)不一樣都伪,功能細(xì)化了一定程度上可以提高ViewModel的復(fù)用性。 - 使用MVVM-C架構(gòu)积担,解耦ViewModel之間的跳轉(zhuǎn)依賴陨晶,剝離ViewModel中的頁面跳轉(zhuǎn)邏輯,來提高ViewModel的復(fù)用性。
Daniel Hall的這篇文章:
The Problems with MVVM on iOS?—?Daniel Hall
工程地址:
參考文獻(xiàn):
深入分析MVC先誉、MVP湿刽、MVVM、VIPER
MVVM Tutorial with ReactiveCocoa
iOS Architecture Patterns
A Better MVC
VIPER and Clean by Uncle Bob.
MVVM
How not to get desperate with MVVM implementation
Highly maintainable app architecture
MVVM is Not Very Good?—?Soroush Khanlou
The Problems with MVVM on iOS?—?Daniel Hall