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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容