前言 MVVMDemo
之前幾個月一直在學習react-native,它的組件化開發真的是很棒,控件和頁面的組件化在開發中可以很好的復用,節省開發時間。在那個時候還不知道react-native開發用到的就是MVVM設計模式。
前幾天,UI給了新的需求,需要添加幾個頁面(之前的項目一直使用MVC開發的),在給這幾個新頁面添加入口的時候,感覺之前寫的代碼真的是好惡心??????,就在網上搜了搜MVP和MVVM,發現MVVM和我在寫RN時的寫法很像。就研究了一下,然后寫下了這篇文章。(可能會有很多問題,歡迎評論)
ps:這篇文章實用為主,那些理論性的東西,我都沒有研究。
俗話說得好:黑貓白貓,能用在項目中的就是好??
更新
解決了,之前存在的button需要在HeaderView中復寫的問題。
吐槽
之前在網上搜索MVVM設計模式的時候,很多文章都說到MVVM和ReactiveCocoa最好是搭配在一起使用,這樣效率更高。我承認ReactiveCocoa是個好東西,但我為什么沒有研究,而只寫了這個簡單的MVVMDemo呢?因為我覺得,雖然直接使用MVVM會有數據綁定方面的問題,但如果為了使用MVVM還要在項目中集成ReactiveCocoa,對團隊開發都不是很友好的,而我本身也只是希望將項目中的一些類拆分,用簡單的MVVM就足夠了,這樣可能省下大量的時間成本去做別的事情。
當然,以后如果有機會,我也可能會將項目使用MVVM和ReactiveCocoa來重構。
使用
MVVM顧名思義,那就是Model,View,ViewModel,所以我們需要創建這些類了。
接下來就把我的理解說說。
ViewModelClass
ViewModelClass.h
中
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
//定義返回請求數據的block類型
// 成功返回的數據
typedef void (^ReturnValueBlock) (id returnValue);
// 失敗返回的數據
typedef void (^ErrorCodeBlock) (id errorCode);
@interface ViewModelClass : NSObject
@property (strong, nonatomic) ReturnValueBlock returnBlock;
@property (strong, nonatomic) ErrorCodeBlock errorBlock;
// 傳入交互的Block塊
-(void) setBlockWithReturnBlock: (ReturnValueBlock) returnBlock
WithErrorBlock: (ErrorCodeBlock) errorBlock;
@end
ViewModelClass.m
#import "ViewModelClass.h"
#import "RTNetworking.h"
@implementation ViewModelClass
#pragma 接收傳過來的block
-(void) setBlockWithReturnBlock: (ReturnValueBlock) returnBlock
WithErrorBlock: (ErrorCodeBlock) errorBlock
{
_returnBlock = returnBlock;
_errorBlock = errorBlock;
}
@end
上面的兩個類放著的就是ViewModel的基類,用下面的方法承接之后繼承于這個基類的VM中的回調數據。
- (void)setBlockWithReturnBlock: (ReturnValueBlock) returnBlock
WithErrorBlock: (ErrorCodeBlock) errorBlock;
ViewController
我之前的項目用的MVC設計模式,C指的就是這個ViewController了,之前寫的垃圾代碼,一個Controller里面放過1000多行代碼,現在去找個方法需要N久。但雖然這是個簡單的例子,但真正利用之后并不簡單。
ViewController.m
// 初始化HeaderVM
HeaderVM *headerView = [[HeaderVM alloc] init];
// 初始化HomeVM
HomeVM *model = [[HomeVM alloc]init];
// 調用ViewModelClass基類的方法,來獲取數據
[model setBlockWithReturnBlock:^(id returnValue) {
_dataArray = returnValue;
_listArray = returnValue[@"picList"];
_categoryArray = returnValue[@"category_new"];
UIView *view = [headerView headerViewWithData:_categoryArray];
self.tableView.tableHeaderView = view;
[self.tableView reloadData];
} WithErrorBlock:^(id errorCode) {
NSLog(@"%@",errorCode);
}];
上面的代碼雖短,但最重要的東西都在里面,通過Block回調,將需要的數據在VM頁面回傳了回來。具體內容見MVVMDemo
HomeVM
HomeVM.h
#import "ViewModelClass.h"
@interface HomeVM : ViewModelClass
// 獲取商品列表
- (void)fetchShopList;
// 跳轉到商品詳情頁
- (void)shopListDetailWithVC:(UIViewController *)vc didSelectRowAtDic:(NSDictionary *)dic;
@end
HomeVM.m
#import "HomeVM.h"
#import "RTNetworking.h"
#import "DetailViewController.h"
@implementation HomeVM
- (void)fetchShopList{
[RTNetworking getWithUrl:@"/v1/Home/all.json" refreshCache:NO success:^(id response) {
[self loadDataWithSuccessDic:response];
} fail:^(NSError *error) {
self.errorBlock(error);
}];
}
- (void)loadDataWithSuccessDic:(NSDictionary *)dic{
NSMutableArray *arr = dic[@"data"];
self.returnBlock(arr);
}
- (void)shopListDetailWithVC:(UIViewController *)vc didSelectRowAtDic:(NSDictionary *)dic{
DetailViewController *view = [[DetailViewController alloc]init];
view.labelText = dic[@"title"];
[vc.navigationController pushViewController:view animated:YES];
}
@end
可以明顯看出HomeVM
是繼承于ViewModelClass
,在這個VM中,將Push到新頁面的方法也寫在了里面。
HeaderView
這個是tableView的headerView。
HeaderView.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef void(^HeaderViewBlock)(NSString *shopId);
@interface HeaderView : UIView
@property (nonatomic, copy) HeaderViewBlock block;
// headerView中的數據
- (void)headerViewWithData:(id)data;
- (void)setBlock:(HeaderViewBlock)block;
@end
HeaderView.m
#import "HeaderView.h"
#import "UIKit+AFNetworking.h"
#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
@implementation HeaderView{
UIImageView *topImage;
NSMutableArray *dataArray;
UIButton *button;
}
- (void)headerViewWithData:(id)data{
dataArray = data;
CGFloat btnWidth = SCREEN_WIDTH * 0.17;
CGFloat btnHeight = SCREEN_WIDTH * 0.17;
CGFloat margin=(SCREEN_WIDTH-5*btnWidth)/6;
for (int i = 0; i <dataArray.count; i++) {
int row = i % 5;
int loc = i / 5;
CGFloat appviewx=margin+(margin+btnWidth)*row;
CGFloat appviewy=5 + (10+btnHeight)*loc;
button = [UIButton buttonWithType:(UIButtonTypeCustom)];
button.frame = CGRectMake(appviewx, appviewy, btnWidth, btnHeight);
button.highlighted = NO;
button.tag = [dataArray[i][@"id"] integerValue];
[button setImageForState:(UIControlStateNormal) withURL:[NSURL URLWithString:dataArray[i][@"icon"]]];
button.userInteractionEnabled = YES;
[button addTarget:self action:@selector(button:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
}
- (void)button:(UIButton *)btn{
NSString *shopId = [NSString stringWithFormat:@"%ld",(long)btn.tag];
if (self.block) {
self.block(shopId);
}
}
@end
我將HeaderView的布局寫在了這個View里面,還有HeaderView上的按鈕的點擊事件,通過block回調,在主頁面進行頁面跳轉操作。
總結
可能你會發現這個目錄中沒有Model,這是因為我做的這個Demo中用Model太浪費,以后,如果我感覺我對MVVM的理解更深一層的時候,會再寫一篇關于MVVM的文章,敬請期待啦!
這個Demo中的數據用的是我公司首頁的接口,請不要亂用哦!
Demo中用到的網絡請求是我再封裝的一層,用起來還不錯,如果有什么好的建議歡迎提出。