iOS-MVVM-框架介绍

MVVM到底是什么?与其专注于说明MVVM的来历,不如让我们看一个典型的ios是如何构建的,并从那里了解MVVM:
这里写图片描述

我们看到的是一个典型的MVC设置。Model呈现数据,View呈现用户界面,而ViewController调节它两者之间的交互。Cool!

稍微考虑一下,虽然View和ViewController是技术上不同的组件,但它们几乎总是手牵手在一起,成对的。你什么时候看到一个View能够于不同ViewController配对?或者反过来?所以,为什么不正规化她们的链接呢?
这里写图片描述

这更准确地描述了你可能已经编写的 MVC 代码。但它并没有做太多事情来解决 iOS 应用中日益增长的重量级视图控制器的问题。在典型的 MVC 应用里,许多逻辑被放在 View Controller 里。它们中的一些确实属于 View Controller,但更多的是所谓的“表示逻辑(presentation logic)”,以 MVVM 属术语来说,就是那些将 Model 数据转换为 View 可以呈现的东西的事情,例如将一个 NSDate 转换为一个格式化过的 NSString。

我们的图解里缺少某些东西,那些使我们可以把所有表示逻辑放进去的东西。我们打算将其称为 “View Model” —— 它位于 View/Controller 与 Model 之间:
这里写图片描述

看起好多了!这个图解准确地描述了什么是 MVVM:一个 MVC 的增强版,我们正式连接了视图和控制器,并将表示逻辑从 Controller 移出放到一个新的对象里,即 View Model。MVVM 听起来很复杂,但它本质上就是一个精心优化的 MVC 架构,而 MVC 你早已熟悉。

现在我们知道了什么是 MVVM,但为什么我们会想要去使用它呢?在 iOS 上使用 MVVM 的动机,对我来说,无论如何,就是它能减少 View Controller 的复杂性并使得表示逻辑更易于测试。通过一些例子,我们将看到它如何达到这些目标。

此处有三个重点是我希望你看完本文能带走的:
MVVM 可以兼容你当下使用的 MVC 架构。
MVVM 增加你的应用的可测试性。
MVVM 配合一个绑定机制效果最好。
如我们之前所见,MVVM 基本上就是 MVC 的改进版,所以很容易就能看到它如何被整合到现有使用典型 MVC 架构的应用中。让我们看一个简单的 Person Model 以及相应的 View Controller:

@interface Person : NSObject

-(instancetype)initWithSalutation:(NSString*)salutation firstName:(NSString*)firstName lastName:(NSString*)lastName birthdate:(NSDate*)birthdate;

@property(nonatomic,copy) NSString *salutation;
@property(nonatomic,copy) NSString *firstName;
@property(nonatomic,copy) NSString *lastName;
@property(nonatomic,copy) NSString *birthdate;

@end

Cool!现在我们假设我们有一个PersonViewController,在viewDidLoad里,只需要基于它的model属性设置一些Label即可。

-(void)viewDidLoad{
    [super viewDidLoad];
    if(self.model.salutation.length > 0){
        self.nameLabel.text = [NSString stringWithFormat:@"%@ %@ %@", self.model.salutation, self.model.firstName, self.model.lastName]; 
    }
    else{
        self.nameLabel.text = [NSString stringWithFormat:@"%@ %@", self.model.firstName, self.model.lastName];  
    }
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 
    [dateFormatter setDateFormat:@"EEEE MMMM d, yyyy"]; 
    self.birthdateLabel.text = [dateFormatter stringFromDate:model.birthdate]; 
}

这全都直截了当,标准的MVC。现在来看看我们如何用一个ViewModel来增强它。

@interface PersonViewModel : NSObject

-(instancetype)initWithPerson:(Person*)person;

@property(nonatomic,strong) Person *person;
@property(nonatomic,copy) NSString *nameText;
@property(nonatomic,copy) NSString *birthdateText;

@end

我们的View Model的实现大概如下:

@implementation PersonViewModel

-(instancetype)initWithPerson:(Person*)person{
    self = [super init];
    if(!self){
        return nil;
    }
    _person = person;
    if(person.salutation.length > 0){
        _nameText = [NSString stringWithFormat:@"%@ %@ %@", self.person.salutation, self.person.firstName, self.person.lastName];
    }
    else{
        _nameText = [NSString stringWithFormat:@"%@ %@", self.person.firstName, self.person.lastName]; 
    }
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 
    [dateFormatter setDateFormat:@"EEEE MMMM d, yyyy"]; 
    _birthdateText = [dateFormatter stringFromDate:person.birthdate]; 

    return self;
}

@end

Cool!我们已经将 viewDidLoad 中的表示逻辑放入我们的 View Model 里了。此时,我们新的 viewDidLoad 就会非常轻量:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.nameLabel.text = self.viewModel.nameText;
    self.birthdateLabel.text = self.viewModel.birthdateText;
}

所以,如你所见,并没有对我们的 MVC 架构做太多改变。还是同样的代码,只不过移动了位置。它与 MVC 兼容,带来更轻量的 View Controllers。

可测试,嗯?是怎样?好吧,View Controller 是出了名的难以测试,因为它们做了太多事情。在 MVVM 里,我们试着尽可能多的将代码移入 View Model 里。测试 View Controller 就变得容易多了,因为它们不再做一大堆事情,并且 View Model 也非常易于测试。让我们来看看:

SpecBegin(Person) 
    NSString *salutation = @"Dr."; 
    NSString *firstName = @"first"; 
    NSString *lastName = @"last"; 
    NSDate *birthdate = [NSDate dateWithTimeIntervalSince1970:0]; 

    it (@"should use the salutation available. ", ^{ 
        Person *person = [[Person alloc] initWithSalutation:salutation firstName:firstName lastName:lastName birthdate:birthdate]; 
        PersonViewModel *viewModel = [[PersonViewModel alloc] initWithPerson:person]; 
        expect(viewModel.nameText).to.equal(@"Dr. first last"); 
    }); 

    it (@"should not use an unavailable salutation. ", ^{ 
        Person *person = [[Person alloc] initWithSalutation:nil firstName:firstName lastName:lastName birthdate:birthdate]; 
        PersonViewModel *viewModel = [[PersonViewModel alloc] initWithPerson:person]; 
        expect(viewModel.nameText).to.equal(@"first last"); 
    }); 

    it (@"should use the correct date format. ", ^{ 
        Person *person = [[Person alloc] initWithSalutation:nil firstName:firstName lastName:lastName birthdate:birthdate]; 
        PersonViewModel *viewModel = [[PersonViewModel alloc] initWithPerson:person]; 
        expect(viewModel.birthdateText).to.equal(@"Thursday January 1, 1970"); 
    }); 
SpecEnd 

如果我们没有将这个逻辑移入 View Model,我们将不得不实例化一个完整的 View Controller 以及伴随的 View,然后去比较我们 View 中 Lable 的值。这样做不只是会变成一个麻烦的间接层,而且它只代表了一个十分脆弱的测试。现在,我们可以按意愿自由地修改视图层级而不必担心破坏我们的单元测试。使用 MVVM 带来的对于测试的好处非常清晰,甚至从这个简单的例子来看也可见一斑,而在有更复杂的表示逻辑的情况下,这个好处会更加明显。
注意到在这个简单的例子中, Model 是不可变的,所以我们可以只在初始化的时候指定我们 View Model 的属性。对于可变 Model,我们还需要使用一些绑定机制,这样 View Model 就能在背后的 Model 改变时更新自身的属性。此外,一旦 View Model 上的 Model 发生改变,那 View 的属性也需要更新。Model 的改变应该级联向下通过 View Model 进入 View。
在 OS X 上,我们可以使用 Cocoa 绑定,但在 iOS 上我们并没有这样好的配置可用。我们想到了 KVO(Key-Value Observation),而且它确实做了很伟大的工作。然而,对于一个简单的绑定都需要很大的样板代码,更不用说有许多属性需要绑定了。作为替代,我个人喜欢使用 ReactiveCocoa,但 MVVM 并未强制我们使用 ReactiveCocoa。MVVM 是一个伟大的典范,它自身独立,只是在有一个良好的绑定框架时做得更好。
我们覆盖了不少内容:从普通的 MVC 派生出 MVVM,看它们是如何相兼容的范式,从一个可测试的例子观察 MVVM,并看到 MVVM 在有一个配对的绑定机制时工作得更好。如果你有兴趣学习更多关于 MVVM 的知识,你可以看看这篇博客,它用更多细节解释了 MVVM 的好处,或者这一篇关于我们如何在最近的项目里使用 MVVM 获得巨大的成功的文章。我同样还有一个经过完整测试,基于 MVVM 的应用,叫做 C-41 ,它是开源的。

Swift版本最新发布: https://github.com/EasyIOS/EasyIOS-Swift 全新基于MVVM(Model-View-ViewModel)编程模式架构,开启EasyIOS开发函数式编程新篇章。 EasyIOS 2.0类似AngularJs,最为核心的是:MVVM、ORM、模块化、自动化双向数据绑定、等等 关于有疑问什么是MVVM,以及为什么IOS开发需要MVVM思想编程的,请看文章用Model-View-ViewModel构建iOS App有详细介绍. EasyIOS 2.0是基于MVVM编程思想进行构建的,封装了Scene,SceneModel,Model,Action四种模型来对IOS进行开发,4种模型的定义解决了IOS开发中ViewController承担了过多角色而造成的代码质量低下,使得结构思路更加清晰。 1.其中Scene就是ViewController的子类,负责仅仅负责界面的展示逻辑 2.Model数据模型,父类实现了ORM,可以实现json、object、sqlite三者之间的一键转换, 3.SceneModel 视图-数据模型,主要负责 视图与模型的绑定工作,其中binding的工作交给了ReactiveCocoa。 4.SceneModel包含Action成员,Action类主要负责网络数据的请求,数据缓存,数据解析工作 如果你有看Github的Trending Objective-C榜单,那你肯定是见过ReactiveCocoa了。如果你在weibo上关注唐巧、onevcat等国内一线知名开发者。那也应该听说过ReactiveCocoa了。 ReactiveCocoa简称RAC,就是基于响应式编程思想的Objective-C实践,它是Github的一个开源项目,你可以在这里找到它。 采用MKNetworkKit 网络框架,修改了部分功能,底层支持网络缓存,轻松控制是否启用缓存。 采用ReactiveCocoa 框架,实现响应式编程,减少代码复杂度。 集成了开源代码UIGridView 网格视图 集成了开源代码RTLabel 富文本Label 集成SVProgressHUD指示器 集成MJRefresh下拉刷新,有删改 model类整合Jastor的类库和MojoDataBase类库 整合了很多开源的优秀代码 部分函数借鉴了BeeFramework 常用类库: Action 负责网络数据请求 Model 负责数据存储 SceneModel 负责Scene与Model的绑定,调用action进行数据请求 Scene 一个视图相当于UIViewController,提供了快速集成网络请求和下拉刷新上拉加载的方法。 SceneTableView 一个TableView,配合scene提供了集成下拉刷新上拉加载的方法 SceneCollectionView 一个CollectionView,配合scene提供了集成下拉刷新上拉加载的方法 2.0版本更新 架构修改,基于MVVM架构 把SceneModel从Scene中剥离出来,并且加入响应式编程框架ReactiveCocoa ReactiveCocoa中文使用说明教程 ReactiveCocoa2实战 ReactiveCocoa 在github上有开源项目ReactiveCocoa2 1.0.3版本更新 再也不用担心奇葩的图文混排了 新增字体图片支持 资源里的demo 就是一个基于swift和easyios的字体图片演示,可以用来作为图片字典查阅 可扩展的字体库,字需要添加ttf和json文件就可以轻松扩展特殊字体 目前支持4种图片字体FontAwesome、Zocial-Regular、Ionicons、Foundation FontAwesome 4.1 字体库, 包含 439 个图标 Foundation icons 字体库, 包含283 个图标 Zocial Contains 字体库, 包含99 个图标 ionicons 1.5.2 字体库, 包含601 个图标,大部分是 IOS7 style 1.0.2版本更新 fix一些头文件的引用关系,增加了swift头文件支持。 用swift的同学,要设置Objective-C Bridging Header为${PODS_ROOT}/Headers/EasyIOS/swift-bridge.h 1.0.2版本发布到了CocoaPods 1.0.1版本更新 1.增加了ORM支持,从此可以实现json、object、sqlite三者之间的一键转换,可以节省很多代码,是不是很酷。 model类整合了Jastor的类库和MojoDataBase类库 2.修改了Action类中的post的参数,增加了files参数, 因此,action.POST_MSG的时候现在至少要3个参数哦 3.借鉴了beeframework的消息通知机制。。.默默给郭大点个赞。。 4.修复了一个下拉刷新的bug 5.提供了一键打开百度地图、苹果地图、google地图、高德地图发起调用的接口,再也不用担心看地图文档 标签:EasyIOS
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值