IOS架构之MVVM.docx
《IOS架构之MVVM.docx》由会员分享,可在线阅读,更多相关《IOS架构之MVVM.docx(10页珍藏版)》请在冰豆网上搜索。
IOS架构之MVVM
IntroductiontoMVVM
MVVM介绍
IgotmyfirstiOSjobat500pxin2011.IhadbeendoingiOScontractingforafewyearsincollege,butthiswasmyfirst,realiOSgig.IwashiredasthesoleiOSdevelopertomakethebeautifullydesignediPadapp.Inonlysevenweeks,weshippeda1.0andcontinuedtoiterate,addingmorefeaturesand,intrinsically,morecomplexitytothecodebase.
在2011年,我在500PX这家公司得到自己第一份做IOS的工作,事实上,我在大学的几年已经开始做IOS开发的一些承包工作,但是真正的做一个漂亮的IOS项目,还是从这份工作开始的。
我被聘为唯一的iOS开发者将设计精美的iPad应用程序,仅用了七个星期,我们就发布了1.0版本,然后持续不断的迭代新的功能,从本质上讲,我的代码也越来越复杂。
ItfeltattimeslikeIdidn’tknowwhatIwasdoing.Iknewmydesignpatterns–likeanygoodcoder–butIwaswaytooclosetotheproductIwasmakingtoobjectivelymeasuretheefficacyofmyarchitecturaldecisions.Ittookbringinganotherdeveloperonboardtheteamformetorealizethatwewereintrouble.
有时我感觉就像我不知道在做什么。
虽然我知道自己的设计模式——就像任何好的编程人员那样——但我太接近我在做的产品以至于不能客观地衡量我的架构决策的有效性。
当队伍中来了另外一位开发者时,我意识到我们陷入困境了。
EverheardofMVC?
MassiveViewController,somecallit.That’scertainlyhowitfeltatthetime.Iwon’tgointotheembarrassingdetails,butitsufficestosaythatifIhadtodoitalloveragain,Iwouldmakedifferentdecisions.
从没听过MVC?
有人称之为MassiveViewController(重量级视图控制器),这就是我们那时候的感觉。
我不打算介绍令人汗颜的细节,但说实在的,如果我不得不再次重来一次,我绝对会做出不同的决策。
OneofthekeyarchitecturalchangesIwouldmake,andhavemadeinappsI’vedevelopedsincethen,wouldbetouseanalternativetoModel-View-ControllercalledModel-View-ViewModel.
我会修改一个关键架构,并将其带入我从那时起就在开发的各种应用,即使用一种叫做Model-View-ViewModel的架构替换Model-View-Controller。
SowhatisMVVM,exactly?
InsteadoffocusingonthehistoricalcontextofwhereMVVMcamefrom,let’stakealookatwhatatypicaliOSapplookslikeandderiveMVVMfromthere:
所以,MVVM到底是什么?
与其专注于说明MVVM的来历,不如让我们看一个典型的iOS是如何构建的,并从那里了解MVVM:
HereweseeatypicalMVCsetup.Modelsrepresentdata,viewsrepresentuserinterfaces,andviewcontrollersmediatetheinteractionsbetweenthetwoofthem.Cool.
我们看到的是一个典型的MVC设置。
Model呈现数据,View呈现用户界面,而ViewController调节它两者之间的交互。
Cool!
Considerforamomentthat,althoughviewsandviewcontrollersaretechnicallydistinctcomponents,theyalmostalwaysgohand-in-handtogether,paired.Whenisthelasttimethataviewcouldbepairedwithdifferentviewcontrollers?
Orviceversa?
Sowhynotformalizetheirconnection?
稍微考虑一下,虽然View和ViewController是技术上不同的组件,但它们几乎总是手牵手在一起,成对的。
你什么时候看到一个View能够与不同ViewController配对?
或者反过来?
所以,为什么不正规化它们的连接呢?
ThismoreaccuratelydescribestheMVCcodethatyou’reprobablyalreadywriting.Butitdoesn’tdomuchtoaddressthemassiveviewcontrollersthattendtogrowiniOSapps.IntypicalMVCapplications,alotoflogicgetsplacedintheviewcontroller.Someofitbelongsintheviewcontroller,sure,butalotofitiswhat’scalled‘presentationlogic,’inMVVMterms–thingsliketransformingvaluesfromthemodelintosomethingtheviewcanpresent,liketakinganNSDateandturningitintoaformattedNSString.
这更准确地描述了你可能已经编写的MVC代码。
但它并没有做太多事情来解决iOS应用中日益增长的重量级视图控制器的问题。
在典型的MVC应用里,许多逻辑被放在ViewController里。
它们中的一些确实属于ViewController,但更多的是所谓的“表示逻辑(presentationlogic)”,以MVVM属术语来说,就是那些将Model数据转换为View可以呈现的东西的事情,例如将一个NSDate转换为一个格式化过的NSString。
We’remissingsomethingfromourdiagram.Somethingwherewecanplaceallofthatpresentationlogic.We’regoingtocallthisthe'viewmodel’–itwillsitbetweentheview/controllerandthemodel:
我们的图解里缺少某些东西,那些使我们可以把所有表示逻辑放进去的东西。
我们打算将其称为“ViewModel”——它位于View/Controller与Model之间:
Lookingbetter!
ThisdiagramaccuratelydescribeswhatMVVMis:
anaugmentedversionofMVCwhereweformallyconnecttheviewandcontroller,andmovepresentationlogicoutofthecontrollerandintoanewobject,theviewmodel.MVVMsoundscomplicated,butit’sessentiallyadressed-upversionoftheMVCarchitecturethatyou’realreadyfamiliarwith.
看起好多了!
这个图解准确地描述了什么是MVVM:
一个MVC的增强版,我们正式连接了视图和控制器,并将表示逻辑从Controller移出放到一个新的对象里,即ViewModel。
MVVM听起来很复杂,但它本质上就是一个精心优化的MVC架构,而MVC你早已熟悉。
SonowthatweknowwhatMVVMis,whywouldonewanttouseit?
ThemotivationbehindMVVMoniOS,forme,anyway,isthatitreducesthecomplexityofone’sviewcontrollersandmakesone’spresentationlogiceasiertotest.We’llseehowitaccomplishesthesegoalswithsomeexamples.
现在我们知道了什么是MVVM,但为什么我们会想要去使用它呢?
在iOS上使用MVVM的动机,对我来说,无论如何,就是它能减少ViewController的复杂性并使得表示逻辑更易于测试。
通过一些例子,我们将看到它如何达到这些目标。
TherearethreereallyimportantpointsIwantyoutotakeawayfromthisarticle:
MVVMiscompatiblewithyourexistingMVCarchitecture.
MVVMmakesyourappsmoretestable.
MVVMworksbestwithabindingmechanism.
此处有三个重点是我希望你看完本文能带走的:
MVVM可以兼容你当下使用的MVC架构。
MVVM增加你的应用的可测试性。
MVVM配合一个绑定机制效果最好。
Aswesawearlier,MVVMisbasicallyjustaspruced-upversionofMVC,soit’seasytoseehowitcanbeincorporatedintoanexistingappwithatypicalMVCarchitecture.Let’stakeasimplePersonmodelandcorrespondingviewcontroller:
如我们之前所见,MVVM基本上就是MVC的改进版,所以很容易就能看到它如何被整合到现有使用典型MVC架构的应用中。
让我们看一个简单的PersonModel以及相应的ViewController:
@interfacePerson:
NSObject
-(instancetype)initwithSalutation:
(NSString*)salutationfirstName:
(NSString*)firstNamelastName:
(NSString*)lastNamebirthdate:
(NSDate*)birthdate;
@property(nonatomic,readonly)NSString*salutation;
@property(nonatomic,readonly)NSString*firstName;
@property(nonatomic,readonly)NSString*lastName;
@property(nonatomic,readonly)NSDate*birthdate;
@end
Cool.Nowlet’ssaythatwehaveaPersonViewControllerthat,inviewDidLoad,justsetssomelabelsbasedonitsmodelproperty:
Cool!
现在我们假设我们有一个PersonViewController,在viewDidLoad里,只需要基于它的model属性设置一些Label即可。
-(void)viewDidLoad{
[superviewDidLoad];
if(self.model.salutation.length>0){
self.nameLabel.text=[NSStringstringWithFormat:
@"%@%@%@",self.model.salutation,self.model.firstName,self.model.lastName];
}else{
self.nameLabel.text=[NSStringstringWithFormat:
@"%@%@",self.model.firstName,self.model.lastName];
}
NSDateFormatter*dateFormatter=[[NSDateFormatteralloc]init];
[dateFormattersetDateFormat:
@"EEEEMMMMd,yyyy"];
self.birthdateLabel.text=[dateFormatterstringFromDate:
model.birthdate];
}
That’sallfairlystraightforward,vanillaMVC.Nowlet’sseehowwecanaugmentthiswithaviewmodel:
这全都直截了当,标准的MVC。
现在来看看我们如何用一个ViewModel来增强它。
@interfacePersonViewModel:
NSObject
-(instancetype)initWithPerson:
(Person*)person;
@property(nonatomic,readonly)Person*person;
@property(nonatomic,readonly)NSString*nameText;
@property(nonatomic,readonly)NSString*birthdateText;
@end
Ourviewmodel’simplementationwouldlooklikethefollowing:
我们的ViewModel的实现大概如下:
@implementationPersonViewModel
-(instancetype)initWithPerson:
(Person*)person{
self=[superinit];
if(!
self)returnnil;
_person=person;
if(person.salutation.length>0){
_nameText=[NSStringstringWithFormat:
@"%@%@%@",self.person.salutation,self.person.firstName,self.person.lastName];
}else{
_nameText=[NSStringstringWithFormat:
@"%@%@",self.person.firstName,self.person.lastName];
}
NSDateFormatter*dateFormatter=[[NSDateFormatteralloc]init];
[dateFormattersetDateFormat:
@"EEEEMMMMd,yyyy"];
_birthdateText=[dateFormatterstringFromDate:
person.birthdate];
returnself;
}
@end
Cool.We’vemovedthepresentationlogicinviewDidLoadintoourviewmodel.OurnewviewDidLoadmethodisnowverylightweight:
Cool!
我们已经将viewDidLoad中的表示逻辑放入我们的ViewModel里了。
此时,我们新的viewDidLoad就会非常轻量:
-(void)viewDidLoad{
[superviewDidLoad];
self.nameLabel.text=self.viewModel.nameText;
self.birthdateLabel.text=self.viewModel.birthdateText;
}
So,asyoucansee,notalotchangedfromourMVCarchitecture.It’sthesamecode,justmovedaround.It’scompatiblewithMVC,leadstolighterviewcontrollers,andismoretestable.
所以,如你所见,并没有对我们的MVC架构做太多改变。
还是同样的代码,只不过移动了位置。
它与MVC兼容,带来更轻量的ViewControllers。
Testable,eh?
How’sthat?
Well,viewcontrollersarenotoriouslyhardtotestsincetheydosomuch.InMVVM,wetryandmoveasmuchofthatcodeaspossibleintoviewmodels.Testingviewcontrollersbecomesaloteasier,sincethey’renotdoingawholelot,andviewmodelsareveryeasytotest.Let’stakealook:
可测试,嗯?
是怎样?
好吧,ViewController是出了名的难以测试,因为它们做了太多事情。
在MVVM里,我们试着尽可能多的将代码移入ViewModel里。
测试ViewController就变得容易多了,因为它们不再做一大堆事情,并且ViewModel也非常易于测试。
让我们来看看:
SpecBegin(Person)
NSString*salutation=@"Dr.";
NSString*firstName=@"first";
NSString*lastName=@"last";
NSDate*birthdate=[NSDatedateWithTimeIntervalSince1970:
0];
it(@"shouldusethesalutationavailable.",^{
Person*person=[[Personalloc]initWithSalutation:
salutationfirstName:
firstNamelastName:
lastNamebirthdate:
birthdate];
PersonViewModel*viewModel=[[PersonViewModelalloc]initWithPerson:
person];
expect(viewModel.nameText).to.equal(@"Dr.firstlast");
});
it(@"shouldnotuseanunavailablesalutation.",^{
Person*person=[[Personalloc]initWithSalutation:
nilfirstName:
firstNamelastName:
lastNamebirthdate:
birthdate];
PersonViewModel*viewModel=[[PersonViewModelalloc]initWithPerson:
person];
expect(viewModel.nameText).to.equal(@"firstlast");
});
it(@"shouldusethecorrectdateformat.",^{
Person*person=[[Personalloc]initWithSalutation:
nilfirstName:
firstNamelastName:
lastNamebirthdate:
birthdate];
PersonViewModel*viewModel=[[PersonViewModelalloc]initWithPerson:
person];
expect(viewModel.birthdateText).to.equal(@"ThursdayJanuary1,1970");
});
SpecEnd
Ifwehadn’tmovedthislogicintotheviewmodel,we’dhavehadtoinstantiateacompleteviewcontrollerandaccompanyingview,comparingthevaluesinsideourview’slabels.Notonlywouldthathavebeenaninconvenientlevelofindirection,butitalsowouldhaverepresentedaseriouslyfragiletest.Nowwe’refreetomodif