IOS收藏iOS之设计模式IOS

我对iOS开发中使用MVVM的理解和使用(初级)

2016-11-16  本文已影响8077人  挂着铃铛的兔

前言 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中用到的网络请求是我再封装的一层,用起来还不错,如果有什么好的建议欢迎提出。

上一篇下一篇

猜你喜欢

热点阅读