iOS的MVVM模式

前言:iOS的MVVM如何工作呢?各自的职责是什么呢?首先我们先来介绍一下纯粹的MVVM架构模式,再来介绍MVVM的双向绑定。

来看一下MVVM的工作原理图:
mvvm.png

MVVM的通讯关系:

1.View与Model是不直接通讯的。
2.ViewController与Model是不直接通讯的。
3.View只与ViewController/View Model两者发生关系。
4.Model只与View Model通讯。

责任划分:

Model:业务逻辑处理、数据控制(本地数据、网络加载数据)。
View:显示用户可见得视图控件、与用户交互事件。
ViewModel:是组织生成和维护的视图数据层。在这一层开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。
ViewController:界面的生命周期控制和业务间切换控制。

工程目录结构:

mvvm.png

MVVM代码实例:

Model:

//-----------MAnimalsModel----------
#import "MAnimalModel.h"
@protocol MAnimalModelDelegate;

@interface MAnimalsModel : NSObject

@property (nonatomic,weak) id<MAnimalModelDelegate> delegate;
@property (nonatomic,strong) NSArray <MAnimalModel*>*dataSource;

- (void)downloadImageWtihModel:(MAnimalModel *)animalModel;

@end

@protocol MAnimalModelDelegate<NSObject>
- (void)animalShowImage:(MAnimalModel *)animalModel row:(NSInteger)row;
@end


#import "MAnimalsModel.h"

@implementation MAnimalsModel

- (instancetype)init {
    self = [super init];
    if (self) {
        [self loadDataSource];
    }
    return self;
}

- (void)dealloc {
    NSLog(@"%s",__func__);
}

- (NSArray *)loadJson {
    NSString *path = [[NSBundle mainBundle] pathForResource:@"jsonString" ofType:@"json"];
    NSData *jsonData = [NSData dataWithContentsOfFile:path];
    NSError *error = nil;
    id jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData options:(NSJSONReadingMutableLeaves) error:&error];
    if ([jsonObject isKindOfClass:[NSArray class]]) {
        return jsonObject;
    }
    else {
        return nil;
    }
}

- (void)loadDataSource {
    NSArray *array = [self loadJson];
    NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:array.count];
    
    for (NSDictionary *dictionary in array) {
        MAnimalModel *model = [[MAnimalModel alloc] init];
        model.identifier = [[NSDate date] timeIntervalSince1970];
        model.imageUrl = [dictionary objectForKey:@"url"];
        model.name = [dictionary objectForKey:@"name"];
        model.summary = [dictionary objectForKey:@"summary"];
        
        [mutableArray addObject:model];
    }
    
    _dataSource = mutableArray;
}

- (void)downloadImageWtihModel:(MAnimalModel *)animalModel {
    if (animalModel.isLoading || !animalModel.imageUrl || animalModel.imageUrl.length == 0) {
        return;
    }
    
    animalModel.isLoading = YES;
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSURL *imageURL = [NSURL URLWithString:animalModel.imageUrl];
        NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
        if (imageData) {
            animalModel.imageData = imageData;
        }
        NSLog(@"imageData = %@",imageData);
        
        dispatch_async(dispatch_get_main_queue(), ^{
            if (self.delegate && [self.delegate respondsToSelector:@selector(animalShowImage:row:)]) {
                NSInteger row = [self.dataSource indexOfObject:animalModel];
                [self.delegate animalShowImage:animalModel row:row];
            }
        });
    });
}

@end


//---------MAnimalModel----------

@interface MAnimalModel : NSObject

@property (nonatomic,assign) NSInteger identifier;
@property (nonatomic,strong) NSData *imageData;
@property (nonatomic,copy) NSString *imageUrl;
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *summary;
@property (nonatomic,assign) BOOL isLoading;

@end

#import "MAnimalModel.h"

@implementation MAnimalModel

- (void)dealloc {
    NSLog(@"%s",__func__);
}

@end

View:

//---------------TableView-------------
#import "MAnimalsViewModel.h"

@interface MAnimalTableView : UITableView

@property (nonatomic,strong) MAnimalsViewModel *viewModel;

- (void)refreshData;

@end

#import "MAnimalTableView.h"

#import "MAnimalCell.h"

@interface MAnimalTableView()<UITableViewDelegate,UITableViewDataSource,MAnimalViewModelDelegate>

@end

@implementation MAnimalTableView

- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
    self = [super initWithFrame:frame style:style];
    if (self) {
        self.delegate = self;
        self.dataSource = self;
        
        self.viewModel = [[MAnimalsViewModel alloc] init];
        self.viewModel.delegate = self;
    }
    return self;
}

- (void)dealloc {
    NSLog(@"%s",__func__);
}

- (void)refreshData {
    [self.viewModel reloadData];
}

#pragma mark -
#pragma mark -- MAnimalViewModel Delegate
- (void)viewModel:(MAnimalsViewModel *)viewModel reloadRow:(NSInteger)row {
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
    [self reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}

- (void)reloadDataWithViewModel:(MAnimalsViewModel *)viewModel {
    [self reloadData];
}

#pragma mark -
#pragma mark -- Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.viewModel.dataSource.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *reuseIdentifier = @"reuseIdentifier";
    MAnimalCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
    if (!cell) {
        cell = [[MAnimalCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier];
    }
    
    MAnimalViewModel *viewModel = [_viewModel animalEntityWitIndexPath:indexPath.row];
    
    cell.textLabel.text = viewModel.name;
    cell.detailTextLabel.text = viewModel.summary;
    [cell showImageWithData:viewModel.imageData];
    
    return cell;
}

#pragma mark -
#pragma mark -- UITableView Delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewRowAction *deleteAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive
                                                                            title:@"Delete"
                                                                          handler:^(UITableViewRowAction *action,
                                                                                    NSIndexPath *indexPath) {
                                                                              [self.viewModel deleteWithRow:indexPath.row];
                                                                              
                                                                              [tableView beginUpdates];
                                                                              [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
                                                                              [tableView endUpdates];
                                                                          }];
    return @[deleteAction];
}


@end


//-------------Cell--------------
@interface MAnimalCell : UITableViewCell

- (void)showImageWithData:(NSData *)data;

@end

#import "MAnimalCell.h"

@implementation MAnimalCell

- (void)awakeFromNib {
    [super awakeFromNib];
    // Initialization code
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
    [super setSelected:selected animated:animated];

    // Configure the view for the selected state
}

- (void)showImageWithData:(NSData *)data {
    if (data) {
        self.imageView.image = [UIImage imageWithData:data];
    }
}

@end

ViewModel:

//-------------ViewModels--------------
#import "MAnimalViewModel.h"
@protocol MAnimalViewModelDelegate;

@interface MAnimalsViewModel : NSObject

@property (nonatomic,weak) id<MAnimalViewModelDelegate> delegate;
@property (nonatomic,strong,readonly) NSMutableArray <MAnimalViewModel*>*dataSource;

- (MAnimalViewModel *)animalEntityWitIndexPath:(NSInteger)row;
- (void)deleteWithRow:(NSInteger)row;
- (void)reloadData;

@end

@protocol MAnimalViewModelDelegate<NSObject>
- (void)viewModel:(MAnimalsViewModel *)viewModel reloadRow:(NSInteger)row;
- (void)reloadDataWithViewModel:(MAnimalsViewModel *)viewModel;
@end


#import "MAnimalsViewModel.h"

#import "MAnimalsModel.h"

@interface MAnimalsViewModel()<MAnimalModelDelegate>
@property (nonatomic,strong) MAnimalsModel *animalsModel;
@end

@implementation MAnimalsViewModel

- (instancetype)init {
    self = [super init];
    if (self) {
        _animalsModel = [[MAnimalsModel alloc] init];
        _animalsModel.delegate = self;
        
        [self reloadData];
    }
    return self;
}

- (void)dealloc {
    NSLog(@"%s",__func__);
}

- (MAnimalViewModel *)animalEntityWitIndexPath:(NSInteger)row {
    MAnimalViewModel *viewModel = [_dataSource objectAtIndex:row];
    
    if (_animalsModel.dataSource.count > row) {
        MAnimalModel *model = [_animalsModel.dataSource objectAtIndex:row];
        [_animalsModel downloadImageWtihModel:model];
    }
    
    return viewModel;
}

- (void)animalShowImage:(MAnimalModel *)animalModel row:(NSInteger)row {
    MAnimalViewModel *viewModel = [_dataSource objectAtIndex:row];
    viewModel.imageData = animalModel.imageData;
    
    if (_delegate && [_delegate respondsToSelector:@selector(viewModel:reloadRow:)]) {
        [_delegate viewModel:self reloadRow:row];
    }
}

- (void)deleteWithRow:(NSInteger)row {
    [_dataSource removeObjectAtIndex:row];
}

- (void)reloadData {
    MAnimalViewModel *vieweModel = nil;
    
    NSMutableArray *mutableArray = [NSMutableArray array];
    for (MAnimalViewModel *eachViewModel in _animalsModel.dataSource) {
        vieweModel = [[MAnimalViewModel alloc] init];
        vieweModel.identifier = eachViewModel.identifier;
        vieweModel.imageData = eachViewModel.imageData;
        vieweModel.name = eachViewModel.name;
        vieweModel.summary = eachViewModel.summary;
        [mutableArray addObject:vieweModel];
    }
    
    _dataSource = mutableArray;
    
    if (_delegate && [_delegate respondsToSelector:@selector(reloadDataWithViewModel:)]) {
        [_delegate reloadDataWithViewModel:self];
    }
}

@end

//------------------ViewModel-------------
@interface MAnimalViewModel : NSObject

@property (nonatomic,assign) NSInteger identifier;
@property (nonatomic,strong) NSData *imageData;
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *summary;

@end

#import "MAnimalViewModel.h"

@implementation MAnimalViewModel

- (void)dealloc {
    NSLog(@"%s",__func__);
}

@end

ViewController:

#import <UIKit/UIKit.h>

@interface MAnimalViewController : UIViewController

@end

#import "MAnimalViewController.h"

#import "MAnimalTableView.h"

@interface MAnimalViewController ()

@end

@implementation MAnimalViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.title = @"MVVM";
    
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Refresh"
                                                                              style:UIBarButtonItemStylePlain
                                                                             target:self
                                                                             action:@selector(refreshAction)];
    
    MAnimalTableView *tableView = [[MAnimalTableView alloc] initWithFrame:self.view.frame style:UITableViewStylePlain];
    self.view = tableView;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)refreshAction {
    MAnimalTableView *tableView = (MAnimalTableView *)self.view;
    [tableView refreshData];
}

@end

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

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_x阅读 15,967评论 3 119
  • 如果面试出错没得到工作或者失恋,这并不可怕,而且这还是一次总结教训的学习机会,也许我们需要面试十次,谈五次恋爱才能...
    圆洲绿阅读 405评论 0 0
  • 十年寒窗谁曾忆,一榜题名天下闻 秋风引情遥相望,尽日菊花气氤氲 长龙啸望鸣川海,凤绕苍浪渺众芸 他...
    小熊nono阅读 158评论 1 1
  • ​ 世界依然风风雨雨,人生过客还是你 2016年7月25日。 西安,小寨赛格,傍晚暴雨倾盆。 晚上10点20,结束...
    营家课堂阅读 212评论 0 1
  • 在webStorm的sass模版下,只需要修改Argumens中的参数即可.具体而言,就是"要编译的sass文件"...
    卡布奇洛猫阅读 208评论 0 0