iOS开发之常用技术点

IOS架构之-MVVM架构/MVVM-C架构

2018-05-01  本文已影响359人  leonardni

文章结构


1.MVX架构问题
1.1理解Model层
1.2 万恶的ViewController
1.3 View的复用性
2.什么是MVVM
2.1MVVM各层的职责

修改记录

  1. 将RWTFlickrSearch工程model层业务逻辑实例方法实现方式替换成类方法实现。

一、MVX架构问题


1.1理解Model层:

M层要完成对业务逻辑实现的封装,一般业务逻辑最多的是涉及到客户端和服务器之间的业务交互。M层里面要完成服务器之间交互以及本地缓存和数据库存储(COREDATA, SQLITE,其他)等所有业务实现的封装,并向外提供的成员方法供其他层使用,而不是简简单单的数据结构。

那么这层业务逻辑实现的方法应该放在哪?

目前看到的有两种做法:

  1. DataMannage类集合所有当前页Model层数据请求
  2. 下面工程中所使用的协议法,定义相关的接口函数,在相应的类中方法,方便进一步细化。
  3. 直接写在Model类中
    |-- 属性
    |-- 类方法
    第三种方式目前没有看到有工程这么使用,建议还是使用上面两种方式。

方式一的工程文件结构:


看下APIMannager的具体实现:
工程地址:
MVX架构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;

原工程作者这里是把整个app的网罗请求都放在一个类里,不建议这么做,工程越大这里的方法便会越多,可以再细分细分成某个模块或者某个界面的数据逻辑,而不是整个app所有的数据请求比如:PageDetailAPIMannager。

第二种方式下面结合具体工程我们在讲。

1.2 万恶的ViewController:

1.在MVC架构中,因为ViewController 类中包含了self.view 导致很多新手会把View的初始化布局和C的逻辑都写在ViewController中,View层和C层划分不明确。
2.因为UIKIt 框架的限制,页面跳转时不得不依赖viewController。

1.3 View的复用性

我们经常在工程中看到类似的代码:

//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;
}

因为View与Model的耦合导致View的复用时候不得不重新抽离出基类view,再继承实现不同的赋值逻辑。复用性降低。
建议将View中的控件赋值剥离出来,来提高View的服用性。
目前看到两种做法:
1.用一个专门的viewHelper类实现model与view的赋值。
2.使用抽象类定义 binddata方法,再在具体view实现binddata方法。
两种方法原理类似,都需要对View进行一定的封装,只是数据赋值的方式有点不同。

方式一:

深入分析MVC、MVP、MVVM、VIPER

#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;
    
    //点赞的业务逻辑
    __weak typeof(cell) weakCell = cell;
    [cell setDidLikeHandler:^{
        if (cellHelper.blog.isLiked) {
            [self.tableView showToastWithText:@"你已经赞过它了~"];
        } 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;
                    //点赞的业务展示
                    weakCell.likeState = cellHelper.blog.isLiked;
                    weakCell.likeCountText = cellHelper.blogTitleText;
                }
            }];
        }
    }];
    return cell;
}

BlogCellHelper类处理model数据,格式化后再赋值给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;
}

与上面原理一致,同样需要对view进行一定的封装。好处是这样binddata就可以在view内部写赋值逻辑,减少viewController负担。要复用时,只要在继承类里面覆盖binddata方法就行。

这里指对view封装是指:一些复杂的view组件可能根据数据的type字段的不同类型,UI布局有显示有很大的变化时,就需要对view布局相关的方法进行一定的封装,给外部调用。

这样的方式会增加一部分工作量,但因为剥离了对具体数据的依赖,View的可复用性大大提高。

二、什么是MVVM


先弄张图来看下什么是MVVM:


MVC
MVP
MVVM

不管是MVC、MVP、MVVM有没有发现一些规律:
中心类:MVC、MVP、MVVM中心类分别是C /P/VM 。

中心类与View

不管是MVC、MVP、MVVM,View都是将用户事件传递给中心类, 中心类负责View层的数据更新。
而MVVM比MVP只是多了一层数据双向绑定,View根据相应的ViewModel自动变化。

中心类与Model

向model层调用业务逻辑实现方法请求数据,并根据需要更新model数据。

找到这层规律,我们再看MVVM就非常简单明了了。

2.1 MVVM各层的职责:

2.1.1 Model层

Model层各个MVX架构都相同:

1.提供业务逻辑实现方法(本地存储数据的交互、服务端数据的交互)
2.数据结构

2.1.2 View层(View/ViewController)

1.负责View的初始化释放与布局
2.响应用户交互事件

2.1.3 VIewModel层

  1. 从Model层 获取数据
  2. 同步数据到Model中
  3. 向View层提供接口,其中包括 数据接口(格式化的数据) 以及事件处理接口
  4. 通知View层数据改变。
  5. 业务逻辑
  6. 页面跳转

看到这些任务不少小伙伴肯定觉得似曾相识,这不就是MVC架构中C层需要负责的任务吗?是的,就是因为这样有人才提出MVVM实则就是把MVC架构中的C层的任务剥离出来放在了ViewModel层实现,于是从臃肿的C变成了臃肿的ViewModel,可复用性的C变成了可复用的性的ViewModel。

MVVM架构的缺点以及缺点的改进方法在下文我会跟大家细说。在这里这么讲,主要是帮助大家理解viewModel的职责,方便大家实际项目应用,大致相同与于我们之前写的MVC架构的C层。

2.2 结合工程我们抽几个关键代码来看下各层的实现:

工程目录结构



绿色部分圈出两个看起来有点奇怪的区域,等下我们再讲。先帖出几个关键性的代码:

Model层:



分两部分组成:

  1. 红色框: 业务逻辑实现
  2. 绿色框: 数据结构
业务逻辑实现

业务逻辑层部分: 由协议(RWTFlickrSearch)定义业务逻辑函数接口,实现类(RWTFlickrSearchImpl)实现具体的接口函数方式,定义Model层的业务逻辑层。
像下面这样:

RWTFlickrSearch协议

#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协议(抽象类的思想),具体函数实现由一个或者多个IMPL类实现,方便业务逻辑实现进一步拆分细化。

在ViewModel层你就可以看到类似这样的调用方式:

@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

直接调用类方法,来调用具体业务逻辑实现方法。

数据结构
#import <Foundation/Foundation.h>

@interface RWTFlickrPhoto : NSObject

@property (strong, nonatomic) NSString *title;
@property (strong, nonatomic) NSURL *url;
@property (strong, nonatomic) NSString *identifier;

@end

数据结构比较简单就不讲了。

View层



和我们平时写的MVC层没有什么区别,贴几个代表性的代码出来看下。

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层

  1. view视图布局
  2. view与ViewModel数据双向绑定
  3. 调用ViewModel提供的用户事件处理接口

比如这里的searchButton的点击搜索事件,与具体的ViewModel事件接口executeSearch相绑定,viewModel内部处理用户点击搜索的业务逻辑。

tip:


看下RACCommad的官方解释,按钮的点击事件会触发RACCommad任务的执行,同时也会将当前按钮的使能和RACCommand的canExecute
触发了事件的同时,又控制了按钮的使能防止重复触发一举两得。

ViewModel相关信息需要View层展示给用户的,也是在数据绑定这一步实现。比如这里的self.viewModel.errorSignal信号。
errorSignal信号的初始化如下:

- (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数据显示
@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;
//错误信息显示
@property (nonatomic,strong) RACSignal *errorSignal;
@property (nonatomic,weak) id<RWTFlickrSearchViewModelDelegate>delegate;
@end

提供了View需要的数据接口,以及用户事件响应接口。

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];
             }];
}

因为这里用于搜索页面,所以拥有没有具体的数据Model。需要了解的小伙伴可以看下统一工程目录下的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];
   }];
   //从隐藏状态切换到出现1s后 请求数据
   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

基本上就是我上面讲的实现下面几个

  1. 从Model层 获取数据
  2. 同步数据到Model中(这里没有得到具体的体现)
  3. 向View层提供接口,其中包括 数据接口(格式化的数据) 以及事件处理接口
  4. 通知View层数据改变。
  5. 业务逻辑
  6. 页面跳转

页面跳转
这里是用coordinate的思想做的跳转,你也可以对Vc进行相关的弱引用再调用vc的方法跳转或者是RWTFlickrSearch工程中作者使用的抽象类的方式)

IOS架构之--使用Coordinator提高VC/ViewModel复用性

这一层大家有疑惑的可能主要是RAC的使用(RAC这个框架比较重,当实现MVVM主要是用的它的数据绑定功能,这块还是容易上手的,看两篇文章就行)。
好了,到这里一个完整的MVVM架构就讲完了。

原工程作者的博客:
MVVM Tutorial with ReactiveCocoa
工程地址:
RWTFlickrSearch
原作者在工程中用RAC实现了许多巧妙的用处,可以参考下原工程RAC的使用方法。

Reactive Cocoa文章专题


Reactive Cocoa不熟的小伙伴看一下下面几篇文章:
ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2
ReactiveCocoa Tutorial – The Definitive Introduction: Part 2/2
ReactiveCocoa进阶

ReactiveCocoa v2.5 源码解析之架构总览

三、MVVM问题以及改进方式


介于MVVM ViewModel担任了太多的任务,有人提出MVVM几个以下的缺点:

1.繁重的ViewModel代替了繁重的C
2.不可复用的ViewModel代替了不可复用的C
3.没有数据绑定工具的MVVM代码将会变得非常复杂,MVVM的实际重心在哪?如果是数据绑定那么数据绑定为什么在MVVM架构中没有体现。

优化改进方式:

  1. Daniel Hall提出可以通过将viewModel按照Data Source,Binding,Responder三类功能进一步细分,这么做可能会导致架构与传统的MVVM结构工程结构上看起来有点不一样,功能细化了一定程度上可以提高ViewModel的复用性。
  2. 使用MVVM-C架构,解耦ViewModel之间的跳转依赖,剥离ViewModel中的页面跳转逻辑,来提高ViewModel的复用性。

Daniel Hall的这篇文章:
The Problems with MVVM on iOS — Daniel Hall

工程地址:

MVVM-C Demo

参考文献:


深入分析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

上一篇下一篇

猜你喜欢

热点阅读