谈谈MVVM和链式网络请求架构
前言
前一段时间一直在学习iOS的架构。为什么呢?
公司的架构一直是MVC,当我们正式上线的时候,项目已经有了超十万行代码。主要的VC一般都有2000行代码以上。
关键是,目前版本我们只做了三分之一的业务逻辑…
所以,架构重构吧。
正文
MVVM
MVVM: Model-View-ViewModel
MVVM其实是MVC的进化版,它将业务逻辑从VC中解耦到ViewModel,来实现VC大’瘦身’。
用代码解释吧!
做一个简单的登录判断:
创建LoginViewModel(逻辑处理),LoginModel(只放数据),LoginViewController。
这里不用LoginView是为了让初学者能更好的把精力集中在用ViewModel解耦上。
当然要是你这些都明白,你可以直接看Wzxhaha/RandomerFramework,这是我在做的独立项目Randomer的基本架构(SubClasses+Protocol+MVVM+RAC)以及它的登录注册模块。另外,感谢王隆帅的这篇文章为我打开了新世界的大门。
在LoginModel中加入方法
//.h - (instancetype)initWithUserName:(NSString *)username password:(NSString *)password; @property (nonatomic,copy,readonly)NSString * username; @property (nonatomic,copy,readonly)NSString * password; //.m - (instancetype)initWithUserName:(NSString *)username password:(NSString *)password { if (self = [super init]) { _username = username; _password = password; } return self; }
这个没什么好讲的,就是给Model加一个初始化方法。
在LoginViewModel中加入方法
#import "PersonModel.h" - (instancetype)initWithPerson:(PersonModel *)person; @property (nonatomic,assign,readonly)BOOL canLogin; - (instancetype)initWithPerson:(PersonModel *)person { if (self = [super init]) { //在这做你绑定model后的处理 _canLogin = [self valiCanLoginWithUserName:person.username password:person.password]; } return self; } - (BOOL)valiCanLoginWithUserName:(NSString *)username password:(NSString *)password { if (username.length & password.length) { return YES; } else { return NO; } }
给ViewModel添加个绑定Model的初始化方法,以及判断帐号密码是否有效的方法。
然后VC(或者View)就可以直接这样获得判断后的结果
PersonModel * person = [[PersonModel alloc]initWithUserName:@"10" password:@"10"]; PersonViewModel * viewModel = [[PersonViewModel alloc]initWithPerson:person]; NSLog(@"%d",viewModel.canLogin);
简单的功能的时候没什么,当你处理复杂的逻辑判断的时候,MVVM会有巨大优势。
顺便讲一下ReactiveCocoa,我之所以这么推崇MVVM,主要就是因为RAC和MVVM简直太配了!
ReactiveCocoa
RAC具有函数式编程和响应式编程的特性,要是对编程思想不熟的可以看我的WZXProgrammingIdeas
RAC最大的用处就是能监听到各个事件,RAC把这个叫做信号流,然后接受信号通过block回调,里面大量的使用了block,所以一定要用好@weakify(self)和@strongify(self)。
为什么说RAC和MVVM太配了?
MVVM是把方法解耦到ViewModel,但是还是要VC(V)调用的,那么判断什么时候调用的逻辑还是会复杂。
而RAC解决了这个问题,它负责监听事件,然后调用ViewModel来进行逻辑判断。
例如:
[[_registerBtn rac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(id x) { @strongify(self) [self.viewModel toRegisterWithType:Register]; }]; [[_loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(id x) { @strongify(self) [self.viewModel loginWithUserName:self.usernameTextField.text password:self.usernameTextField.text Success:^(idresponse) { } failure:^{ SHOW_ERROR(@"错误", @"账号或密码错误") } error:^(NSError *error) { SHOW_ERROR(@"错误", @"网络连接失败") }]; }];
RAC监听了登录和注册按钮,使得代码简洁,而且结构十分紧凑。
Demo的话还是看这个吧Wzxhaha/RandomerFramework
https://github.com/Wzxhaha/RandomerFramework
或者简单版的WZXRACDemo
https://github.com/Wzxhaha/WZXRACDemo
链式网络请求框架
为什么封装WZXNetworking
这是一个容错性非常吓人的框架。
[[WZXNetworkManagermanager].setRequest(@"http://192.168.1.40:8001").RequestType(POST).HTTPHeader(nil).Parameters(nil).RequestSerialize(RequestSerializerHTTP).ResponseSerialize(ResponseSerializerJSON) startRequestWithSuccess:^(id response) { NSLog(@"success"); } failure:^{ NSLog(@"failure"); }];
在这里除了.setRequest(url)和startRequestWithSuccess failure方法,其他都是非必要的。
你可以这样:
[[WZXNetworkManager manager].setRequest(@"http://192.168.1.40:8001") startRequestWithSuccess:^(id response) { NSLog(@"success"); } failure:^{ NSLog(@"failure"); }];
链式在参数和参数的选择很多的情况或者很有可能改动的情况下展现了惊人的优势。因为,它的改动十分方便,只不过添加或者修改一个方法。
打个比方:
换成集中式API封装应该是这样的:
- (void)GET:(NSString *)url parameters:(id)Parameters success:(SuccessBlock)success failure:(FailureBlock)failure;
当你要添加一个Version属性做API版本判断的时候,你能怎么办?只能重写方法,在方法中加入一个Version参数,然后所有使用的网络请求都要改变方法。
换成分布式API封装我们则不考虑对比了..
GeneralAPI *apiGeGet = [[GeneralAPI alloc] initWithRequestMethod:@"get"]; apiGeGet.apiRequestMethodType = RequestMethodTypeGET; apiGeGet.apiRequestSerializerType = RequestSerializerTypeHTTP; apiGeGet.apiResponseSerializerType = ResponseSerializerTypeHTTP; [apiGeGet setApiCompletionHandler:^(id responseObject, NSError * error) { NSLog(@"responseObject is %@", responseObject); if (error) { NSLog(@"Error is %@", error.localizedDescription); } }]; [apiGeGet start];
这样的结构是否太松散?
再换成WZXNetworking
我们要做的只是再添加一个方法和一个成员变量,然后在原有方法后面加一个.method()
- (WZXNetworkManager * (^) (id some))method { return ^WZXNetworkManager (id some) { self.XXX = some return self; } }
[[WZXNetworkManager manager].setRequest(@"http://192.168.1.40:8001").method(some) startRequestWithSuccess:^(idresponse) { NSLog(@"success"); } failure:^{ NSLog(@"failure"); }];
代码放这:WZXNetworking