我對iOS開發中使用MVVM的理解和使用(初級)

前言 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,所以我們需要創建這些類了。


項目目錄.png

接下來就把我的理解說說。

ViewModelClass

ViewModelClass.png

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.png

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.png

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中用到的網絡請求是我再封裝的一層,用起來還不錯,如果有什么好的建議歡迎提出。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容