美文网首页iOS开发之常用技术点
IOS架构之-MVVM架构/MVVM-C架构

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

作者: leonardni | 来源:发表于2018-05-01 22:09 被阅读359次

    文章结构


    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

    相关文章

      网友评论

        本文标题:IOS架构之-MVVM架构/MVVM-C架构

        本文链接:https://www.haomeiwen.com/subject/cabtrftx.html