前言
前一段時間一直在學習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
中加入方法
1 2 3 4 5 |
//.h - (instancetype)initWithUserName:(NSString *)username password:(NSString *)password; @property (nonatomic,copy,readonly)NSString * username; @property (nonatomic,copy,readonly)NSString * password; |
1 2 3 4 5 6 7 8 |
//.m - (instancetype)initWithUserName:(NSString *)username password:(NSString *)password { if (self = [super init]) { _username = username; _password = password; } return self; } |
這個沒什麼好講的,就是給Model加一個初始化方法。
在LoginViewModel
中加入方法
1 2 3 4 |
#import "PersonModel.h" - (instancetype)initWithPerson:(PersonModel *)person; @property (nonatomic,assign,readonly)BOOL canLogin; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
- (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)就可以直接這樣獲得判斷後的結果
1 2 3 4 |
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來進行邏輯判斷。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[[_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:^(id response) { } failure:^{ SHOW_ERROR(@"錯誤", @"賬號或密碼錯誤") } error:^(NSError *error) { SHOW_ERROR(@"錯誤", @"網路連線失敗") }]; }]; |
RAC監聽了登入和註冊按鈕,使得程式碼簡潔,而且結構十分緊湊。
Demo的話還是看這個吧Wzxhaha/RandomerFramework
或者簡單版的WZXRACDemo
鏈式網路請求框架
為什麼封裝WZXNetworking
這是一個容錯性非常嚇人的框架。
1 2 3 4 5 6 7 |
[[WZXNetworkManager manager].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
方法,其他都是非必要的。
你可以這樣:
1 2 3 4 5 6 7 |
[[WZXNetworkManager manager].setRequest(@"http://192.168.1.40:8001") startRequestWithSuccess:^(id response) { NSLog(@"success"); } failure:^{ NSLog(@"failure"); }]; |
鏈式在引數和引數的選擇很多的情況或者很有可能改動的情況下展現了驚人的優勢。因為,它的改動十分方便,只不過新增或者修改一個方法。
打個比方:
換成集中式API封裝應該是這樣的:
1 2 3 4 |
- (void)GET:(NSString *)url parameters:(id)Parameters success:(SuccessBlock)success failure:(FailureBlock)failure; |
當你要新增一個Version屬性做API版本判斷的時候,你能怎麼辦?只能重寫方法,在方法中加入一個Version引數,然後所有使用的網路請求都要改變方法。
換成分散式API封裝我們則不考慮對比了..
1 2 3 4 5 6 7 8 9 10 11 |
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()
1 2 3 4 5 6 |
- (WZXNetworkManager * (^) (id some))method { return ^WZXNetworkManager (id some) { self.XXX = some return self; } } |
1 2 3 4 5 6 7 |
[[WZXNetworkManager manager].setRequest(@"http://192.168.1.40:8001").method(some) startRequestWithSuccess:^(id response) { NSLog(@"success"); } failure:^{ NSLog(@"failure"); }]; |
程式碼放這:WZXNetworking
至於鏈式是怎麼實現的,還是看那個WZXProgrammingIdeas
最後
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式