談談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
https://github.com/Wzxhaha/WZXNetworking
至于鏈式是怎么實現的,還是看那個WZXProgrammingIdeas