談?wù)凪VVM和鏈?zhǔn)骄W(wǎng)絡(luò)請(qǐng)求架構(gòu)
前言
前一段時(shí)間一直在學(xué)習(xí)iOS的架構(gòu)。為什么呢?
公司的架構(gòu)一直是MVC,當(dāng)我們正式上線的時(shí)候,項(xiàng)目已經(jīng)有了超十萬(wàn)行代碼。主要的VC一般都有2000行代碼以上。
關(guān)鍵是,目前版本我們只做了三分之一的業(yè)務(wù)邏輯…
所以,架構(gòu)重構(gòu)吧。
正文
MVVM
MVVM: Model-View-ViewModel
MVVM其實(shí)是MVC的進(jìn)化版,它將業(yè)務(wù)邏輯從VC中解耦到ViewModel,來(lái)實(shí)現(xiàn)VC大’瘦身’。
用代碼解釋吧!
做一個(gè)簡(jiǎn)單的登錄判斷:
創(chuàng)建LoginViewModel(邏輯處理),LoginModel(只放數(shù)據(jù)),LoginViewController。
這里不用LoginView是為了讓初學(xué)者能更好的把精力集中在用ViewModel解耦上。
當(dāng)然要是你這些都明白,你可以直接看Wzxhaha/RandomerFramework,這是我在做的獨(dú)立項(xiàng)目Randomer的基本架構(gòu)(SubClasses+Protocol+MVVM+RAC)以及它的登錄注冊(cè)模塊。另外,感謝王隆帥的這篇文章為我打開(kāi)了新世界的大門。
在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;
- }
這個(gè)沒(méi)什么好講的,就是給Model加一個(gè)初始化方法。
在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添加個(gè)綁定Model的初始化方法,以及判斷帳號(hào)密碼是否有效的方法。
然后VC(或者View)就可以直接這樣獲得判斷后的結(jié)果
- PersonModel * person = [[PersonModel alloc]initWithUserName:@"10" password:@"10"];
- PersonViewModel * viewModel = [[PersonViewModel alloc]initWithPerson:person];
- NSLog(@"%d",viewModel.canLogin);
簡(jiǎn)單的功能的時(shí)候沒(méi)什么,當(dāng)你處理復(fù)雜的邏輯判斷的時(shí)候,MVVM會(huì)有巨大優(yōu)勢(shì)。
順便講一下ReactiveCocoa,我之所以這么推崇MVVM,主要就是因?yàn)镽AC和MVVM簡(jiǎn)直太配了!
ReactiveCocoa
RAC具有函數(shù)式編程和響應(yīng)式編程的特性,要是對(duì)編程思想不熟的可以看我的WZXProgrammingIdeas
RAC***的用處就是能監(jiān)聽(tīng)到各個(gè)事件,RAC把這個(gè)叫做信號(hào)流,然后接受信號(hào)通過(guò)block回調(diào),里面大量的使用了block,所以一定要用好@weakify(self)和@strongify(self)。
為什么說(shuō)RAC和MVVM太配了?
MVVM是把方法解耦到ViewModel,但是還是要VC(V)調(diào)用的,那么判斷什么時(shí)候調(diào)用的邏輯還是會(huì)復(fù)雜。
而RAC解決了這個(gè)問(wèn)題,它負(fù)責(zé)監(jiān)聽(tīng)事件,然后調(diào)用ViewModel來(lái)進(jìn)行邏輯判斷。
例如:
- [[_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(@"錯(cuò)誤", @"賬號(hào)或密碼錯(cuò)誤")
- } error:^(NSError *error) {
- SHOW_ERROR(@"錯(cuò)誤", @"網(wǎng)絡(luò)連接失敗")
- }];
- }];
RAC監(jiān)聽(tīng)了登錄和注冊(cè)按鈕,使得代碼簡(jiǎn)潔,而且結(jié)構(gòu)十分緊湊。
Demo的話還是看這個(gè)吧Wzxhaha/RandomerFramework
https://github.com/Wzxhaha/RandomerFramework
或者簡(jiǎn)單版的WZXRACDemo
https://github.com/Wzxhaha/WZXRACDemo
鏈?zhǔn)骄W(wǎng)絡(luò)請(qǐng)求框架
為什么封裝WZXNetworking
這是一個(gè)容錯(cuò)性非常嚇人的框架。
- [[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");
- }];
鏈?zhǔn)皆趨?shù)和參數(shù)的選擇很多的情況或者很有可能改動(dòng)的情況下展現(xiàn)了驚人的優(yōu)勢(shì)。因?yàn)椋母膭?dòng)十分方便,只不過(guò)添加或者修改一個(gè)方法。
打個(gè)比方:
換成集中式API封裝應(yīng)該是這樣的:
- - (void)GET:(NSString *)url
- parameters:(id)Parameters
- success:(SuccessBlock)success
- failure:(FailureBlock)failure;
當(dāng)你要添加一個(gè)Version屬性做API版本判斷的時(shí)候,你能怎么辦?只能重寫方法,在方法中加入一個(gè)Version參數(shù),然后所有使用的網(wǎng)絡(luò)請(qǐng)求都要改變方法。
換成分布式API封裝我們則不考慮對(duì)比了..
- 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];
這樣的結(jié)構(gòu)是否太松散?
再換成WZXNetworking
我們要做的只是再添加一個(gè)方法和一個(gè)成員變量,然后在原有方法后面加一個(gè).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
https://github.com/Wzxhaha/WZXNetworking
至于鏈?zhǔn)绞窃趺磳?shí)現(xiàn)的,還是看那個(gè)WZXProgrammingIdeas