iOS 你真的了解MVC嗎累驮?(附詳細(xì)例子說明)

目錄
  • MVC的簡介
  • MVC三者的職責(zé)和關(guān)系
  • 標(biāo)準(zhǔn)MVC與非標(biāo)準(zhǔn)MVC的區(qū)別及利弊
  • 項(xiàng)目實(shí)戰(zhàn)應(yīng)用(標(biāo)準(zhǔn)MVC寫法與非標(biāo)準(zhǔn)MVC寫法對比)
    • view上面的用戶行為事件如何處理惭蟋?
    • 如何更新對應(yīng)的數(shù)據(jù)模型鹦肿?
    • 數(shù)據(jù)模型更新了后如何處理?
    • 如何更新 view 上面的視圖元素?
簡介

摘自百度百科對MVC的解釋 MVC

MVC (全名是Model View Controller玫镐,是模型(model)-視圖(view)-控制器(controller)的縮寫倒戏,一種軟件設(shè)計(jì)典范,用一種業(yè)務(wù)邏輯恐似、數(shù)據(jù)杜跷、界面顯示分離的方法組織代碼,將業(yè)務(wù)邏輯聚集到一個(gè)部件里面矫夷,在改進(jìn)和個(gè)性化定制界面及用戶交互的同時(shí)葛闷,不需要重新編寫業(yè)務(wù)邏輯。MVC被獨(dú)特的發(fā)展起來用于映射傳統(tǒng)的輸入双藕、處理和輸出功能在一個(gè)邏輯的圖形化用戶界面的結(jié)構(gòu)中淑趾。

三者的職責(zé)

  • Model(模型)是應(yīng)用程序中用于處理應(yīng)用程序數(shù)據(jù)邏輯的部分,通常模型對象負(fù)責(zé)在數(shù)據(jù)庫中存取數(shù)據(jù)忧陪。

  • View(視圖)是應(yīng)用程序中處理數(shù)據(jù)顯示的部分扣泊,通常視圖是依據(jù)模型數(shù)據(jù)創(chuàng)建的近范。

  • Controller(控制器)是應(yīng)用程序中處理用戶交互的部分,通逞有罚控制器負(fù)責(zé)從視圖讀取數(shù)據(jù)评矩,控制用戶輸入,并向模型發(fā)送數(shù)據(jù)等孵。

或許很多小伙伴感覺很疑惑稚照,下面我們就結(jié)合例子詳細(xì)說說MVC

一 初識(shí) MVC

我們先看一副圖蹂空,很清晰明了的描述了三者之間的關(guān)系

MVC.png

三者職責(zé)解釋說明

  • Model層 數(shù)據(jù)處理層俯萌,包括網(wǎng)絡(luò)請求,數(shù)據(jù)加工

  • View層 所有App上看得到的界面

  • Controller層 Model 與 View層的中介上枕,把Model數(shù)據(jù)在View上展示出來

對應(yīng)箭頭解釋

  1. view將用戶交互通知給controller咐熙,通常使用代理。
  2. controller通過更新model來反應(yīng)狀態(tài)的改變辨萍。
  3. model(通常使用KVO)通知controller來更新他們負(fù)責(zé)的view
二 變異的MVC

網(wǎng)上還有另外一幅圖也很形象的表現(xiàn)iOS實(shí)際開發(fā)中MVC架構(gòu)圖

image.png

這張圖是iOS的MVC架構(gòu)中最經(jīng)常出現(xiàn)的圖棋恼,因?yàn)镮OS中的Controlller 是UIViewController,所以導(dǎo)致很多人會(huì)把視圖寫在Controller中锈玉,這樣無疑會(huì)導(dǎo)致VC很臃腫爪飘。

因此,M-VC 可能是對 iOS 開發(fā)中的 MVC模式更為準(zhǔn)確的解讀拉背,同時(shí)更也準(zhǔn)確地描述了我們?nèi)粘i_發(fā)可能已經(jīng)編寫的 MVC 代碼师崎,但它并沒有做太多事情來解決 iOS 應(yīng)用中日益增長的重量級(jí)視圖控制器的問題。

三 MVC的利與弊

在 iOS 開發(fā)中椅棺,MVC(Model View Controller)是構(gòu)建iOS App的標(biāo)準(zhǔn)模式犁罩,是蘋果推薦的一個(gè)用來組織代碼的權(quán)威范式。

Apple甚至是這么說的两疚。在MVC下床估,所有的對象被歸類為一個(gè)Model,一個(gè)View诱渤,和一個(gè)Controller丐巫。Model持有數(shù)據(jù),View顯示與用戶交互的界面勺美,而ViewController調(diào)解Model和View之間的交互〉蓦剩現(xiàn)在,MVC 依然是目前主流客戶端編程框架励烦,但同時(shí)它也被調(diào)侃成Massive View Controller(重量級(jí)視圖控制器)谓着,想必開發(fā)者在開發(fā)中無可避免被下面幾個(gè)問題所困擾:

  • 厚重的ViewController
  • 遺失的網(wǎng)絡(luò)邏輯(無立足之地)
  • 較差的可測試性

接下來就讓我們一起探討MVC的弊端,剖析問題產(chǎn)生原因坛掠,打造一個(gè)輕量級(jí)的ViewController赊锚,明確MVC設(shè)計(jì)模式中各個(gè)角色的職責(zé)治筒。

3.1 厚重的View Controller

Model:模型model的對象通常非常的簡單。根據(jù)Apple的文檔舷蒲,model應(yīng)包括數(shù)據(jù)操作數(shù)據(jù)的業(yè)務(wù)邏輯耸袜。而在實(shí)踐中,model層往往非常薄牲平,不管怎樣堤框,model層的業(yè)務(wù)邏輯不應(yīng)被拖入到controller。

View:視圖view通常是UIKit控件纵柿,View不應(yīng)該直接引用model(PS:現(xiàn)實(shí)中蜈抓,使用了),并且僅僅通過IBAction事件引用controller昂儒。業(yè)務(wù)邏輯很明顯不歸入view沟使,視圖本身沒有任何業(yè)務(wù)。

Controller:Controller是app的膠水代碼:協(xié)調(diào)模型和視圖之間的所有交互渊跋±拔耍控制器負(fù)責(zé)管理他們所擁有的視圖的視圖層次結(jié)構(gòu),還要響應(yīng)視圖的loading拾酝、appearing燕少、disappearing等等,同時(shí)往往也會(huì)充滿我們不愿暴露的model的模型邏輯以及不愿暴露給視圖的業(yè)務(wù)邏輯蒿囤。

網(wǎng)絡(luò)數(shù)據(jù)的請求及后續(xù)處理客们,本地?cái)?shù)據(jù)庫操作,以及一些帶有工具性質(zhì)輔助方法都加大了Massive View Controller的產(chǎn)生蟋软。

3.2 遺失(無處安放)的網(wǎng)絡(luò)邏輯

蘋果使用的MVC的定義是這么說的:所有的對象都可以被歸類為一個(gè)model镶摘,一個(gè)view,或是一個(gè)controller岳守。

你可能試著把它放在Model對象里凄敢,但是也會(huì)很棘手,因?yàn)榫W(wǎng)絡(luò)調(diào)用應(yīng)該使用異步湿痢,這樣如果一個(gè)網(wǎng)絡(luò)請求比持有它的model生命周期更長涝缝,事情將變的復(fù)雜。顯然View里面做網(wǎng)絡(luò)請求那就更格格不入了譬重,因此只剩下Controller了拒逮。若這樣,這又加劇了Massive View Controller的問題臀规。若不這樣滩援,何處才是網(wǎng)絡(luò)邏輯的家呢?

3.3 較差的可測試性

由于View Controller混合了視圖處理邏輯和業(yè)務(wù)邏輯塔嬉,分離這些成分的單元測試成了一個(gè)艱巨的任務(wù)玩徊。

四 項(xiàng)目實(shí)戰(zhàn)
4.1 實(shí)際開發(fā)中MVC的使用

我們仿頭條主頁樣式寫了一個(gè)測試用例租悄,然后來講解實(shí)際開發(fā)的用法。

1.gif

核心類講解

  • NewsModel 新聞模型數(shù)據(jù)類
@interface NewsModel : NSObject

/** id */
@property(nonatomic, copy)NSString *newsId;
/** icon */
@property(nonatomic, copy)NSString *icon;
/** title */
@property(nonatomic, copy)NSString *title;
/** subTitle */
@property(nonatomic, copy)NSString *subTitle;
/** content */
@property(nonatomic, copy)NSString *content;
/** if attention */
@property(nonatomic, assign, getter=isAttention)BOOL attention;
/** imgList */
@property(nonatomic, copy)NSArray *imgs;
/** share number */
@property(nonatomic, assign)NSUInteger shareNum;
/** discuss num */
@property(nonatomic, assign)NSUInteger discussNum;
/** like */
@property(nonatomic, assign)NSUInteger likeNum;
/** if like */
@property(nonatomic, assign,getter=isLike)BOOL like;

@end
  • NewsCell.h 新聞視圖類
@class NewsModel;

@interface NewsCell : UITableViewCell

/** model */
@property(nonatomic, strong)NewsModel *model;

@end
  • NewsCell.m 實(shí)現(xiàn)類
// 1.我們使用懶加載的形式加載視圖
/** icon */
@property(nonatomic, strong)UIImageView *iconImgView;
/** title */
@property(nonatomic, strong)UILabel *titleLbe;
/** subTitle */
@property(nonatomic, strong)UILabel *subTitleLbe;
......  // 粘貼部分代碼

// 2.采用masonry約束布局
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        self.contentView.backgroundColor = [UIColor whiteColor];
        [self drawUI];
    }
    return self;
}

#pragma mark - drawUI

- (void)drawUI {
    [self.contentView addSubview:self.iconImgView];
    [self.contentView addSubview:self.titleLbe];
    [self.contentView addSubview:self.subTitleLbe];

    [self.iconImgView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(44, 44));
        make.top.equalTo(self.contentView.mas_top).offset(10);
        make.leading.equalTo(self.contentView.mas_leading).offset(10);
    }];

    [self.titleLbe mas_makeConstraints:^(MASConstraintMaker *make) {
        make.leading.equalTo(self.iconImgView.mas_trailing).offset(10);
        make.bottom.equalTo(self.iconImgView.mas_centerY).offset(-2);
    }];
    
    [self.subTitleLbe mas_makeConstraints:^(MASConstraintMaker *make) {
        make.leading.equalTo(self.titleLbe.mas_leading);
        make.top.equalTo(self.iconImgView.mas_centerY).offset(2);
    }];
    ......  // 粘貼部分代碼
}

// 3.設(shè)置數(shù)據(jù)
#pragma mark - set

- (void)setModel:(NewsModel *)model {
    _model = model;
    [self.iconImgView sd_setImageWithURL:[NSURL URLWithString:model.icon]];
    
    self.titleLbe.text = model.title;
    [self.titleLbe sizeToFit];
    
    self.subTitleLbe.text = model.subTitle;
    [self.subTitleLbe sizeToFit];
    ......  // 粘貼部分代碼
}

// 4.懶加載形式加載控件
#pragma mark - lazy

- (UIImageView *)iconImgView {
    if (_iconImgView == nil) {
        _iconImgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
        _iconImgView.layer.cornerRadius = 22;
        _iconImgView.layer.masksToBounds = YES;
    }
    return _iconImgView;
}

- (UILabel *)titleLbe {
    if (_titleLbe == nil) {
        _titleLbe = [self getLbeWithFont:16 textColor:[UIColor blackColor]];
    }
    return _titleLbe;
}

- (UILabel *)subTitleLbe {
    if (_subTitleLbe == nil) {
        _subTitleLbe = [self getLbeWithFont:14 textColor:[UIColor grayColor]];
    }
    return _subTitleLbe;
}
  • ViewController 控制器
// tableView也使用懶加載的形式
- (UITableView *)tableView {
    if (_tableView == nil) {
        _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64, kScreenWidth, kScreenHeight - 64) style:UITableViewStyleGrouped];
        _tableView.delegate = self;
        _tableView.dataSource = self;
        _tableView.scrollsToTop = true;
        _tableView.backgroundColor = [UIColor whiteColor];;
        _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
        _tableView.showsVerticalScrollIndicator = NO;
        _tableView.scrollsToTop = YES;
        _tableView.estimatedRowHeight = 250;//預(yù)估高度
        _tableView.rowHeight = UITableViewAutomaticDimension;
        [_tableView registerClass:[NewsCell class] forCellReuseIdentifier:cellId];
        __weak typeof(self) weakSelf = self;
        [_tableView addPullToRefreshWithActionHandler:^{
            [weakSelf refreshData];
        }];
        [_tableView addInfiniteScrollingWithActionHandler:^{
            [weakSelf loadNextPage];
        }];
    }
    return _tableView;
}

// 核心代碼
#pragma mark - UITableViewDataSource

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

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NewsModel *model = [self.dataSource objectAtIndex:indexPath.row];
    NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.model = model;
    return cell;
}

注解說明

  1. 一個(gè)基本的數(shù)據(jù)展示就完成了恩袱,相信很多開發(fā)小伙伴也是這樣做的泣棋,如果沒有涉及到用戶的交互行為,純粹是做展示畔塔,那基本就完工了潭辈。但是實(shí)際開發(fā)中往往不是這樣的。
  2. 比如用戶點(diǎn)擊了分享澈吨,評(píng)論把敢,點(diǎn)贊關(guān)注棚辽,刪除等按鈕技竟,那我們應(yīng)該如何處理數(shù)據(jù)及更新頁面呢?

接下來我們就從以下幾點(diǎn)出發(fā)來闡述標(biāo)準(zhǔn)的MVC和非標(biāo)準(zhǔn)的MVC兩者之間的區(qū)別屈藐。

mvc.png
4.1 view上面的用戶行為事件如何處理?

當(dāng)用戶在視圖上做了點(diǎn)擊熙尉,那我們應(yīng)該如何處理用戶的點(diǎn)擊事件联逻?以本文實(shí)例中點(diǎn)贊為例子說明。

說明:當(dāng)用戶點(diǎn)擊贊或者取消贊检痰,我們需要告知后臺(tái)用戶的行為包归,同時(shí)更新對應(yīng)的視圖。

4.1.1 標(biāo)準(zhǔn)MVC寫法

viewuser action傳給VC铅歼,一共有三種方式可以將view上的行為傳遞給VC公壤,分別是代理block通知椎椰。本文介紹如果使用代理將用戶的行為告知VC厦幅。

  • NewsCell.h (聲明一個(gè)cell的協(xié)議,并定義一些方法)
@protocol NewsCellDelegate <NSObject>
// tap like
- (void)didTapNewsCellLike:(NewsModel *)newsModel;

@end

@interface NewsCell : UITableViewCell
/** delegate */
@property(nonatomic,weak)id<NewsCellDelegate> delegate;

@end
  • NewsCell.m (在點(diǎn)擊回調(diào)方法中調(diào)用該協(xié)議方法)
// 用戶點(diǎn)擊了點(diǎn)贊按鈕
- (void)tapLike {
    if ([self.delegate respondsToSelector:@selector(didTapNewsCellLike:)]) {
        [self.delegate didTapNewsCellLike:self.model];
    }
}
  • ViewController.m (設(shè)置代理并實(shí)現(xiàn)代理方法即可)
@interface ViewController ()<UITableViewDataSource, UITableViewDelegate, NewsCellDelegate>

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NewsModel *model = [self.dataSource objectAtIndex:indexPath.row];
    NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.model = model;
    cell.delegate = self;   // VC作為Cell視圖的代理對象
    return cell;
}

- (void)didTapNewsCellLike:(NewsModel *)newsModel {
    // 處理view上的點(diǎn)擊事件
}
4.1.2 非標(biāo)準(zhǔn)的MVC寫法

有些小伙伴或許想直接在view視圖中處理網(wǎng)絡(luò)慨飘,數(shù)據(jù)确憨,然后更新對應(yīng)視圖,和VC沒有關(guān)系瓤的。

實(shí)例代碼如下

  • NewsCell.m (直接在視圖中調(diào)用網(wǎng)絡(luò)請求并處理數(shù)據(jù))
// 用戶點(diǎn)擊了點(diǎn)贊按鈕
- (void)tapLike {
   // 直接發(fā)起網(wǎng)絡(luò)請求并處理回調(diào)事件
    __weak typeof(self) weakSelf = self;
    [self.model addLike:^(NSDictionary *json) {
        weakSelf.model.like = !weakSelf.model.isLike;
        if (weakSelf.model.isLike) {
            [weakSelf.likeActionView updateImgName:@"like_red"];
            weakSelf.model.likeNum++;
        } else {
            [weakSelf.likeActionView updateImgName:@"like"];
            weakSelf.model.likeNum--;
        }
        [weakSelf.likeActionView updateTitle:[NSString stringWithFormat:@"%lu",(unsigned long)weakSelf.model.likeNum]];
    }];
}

運(yùn)行結(jié)果

1.gif
1.gif

如果在用戶不拖拽的情況下休弃,該方法是可以實(shí)現(xiàn)效果的,但是一旦用戶進(jìn)行了拖拽圈膏,我們知道UITableView是采用重用機(jī)制塔猾,所以對應(yīng)的視圖和模型數(shù)據(jù)都會(huì)發(fā)生變化,我們可以將請求前和請求后的對象地址打印一下就知道了稽坤。

2019-04-14 10:04:16.496819+0800 MVC-Demo[65856:2082156] old model 0x6000011d9680
2019-04-14 10:04:18.745787+0800 MVC-Demo[65856:2082156] new model 0x6000011d9860

由打印結(jié)果可知丈甸,模型對象發(fā)生了變化医增,所以此種方法不可取。

第二種方法

或許有的小伙伴想老虫,在view中完成網(wǎng)絡(luò)請求叶骨,然后將結(jié)果告知VC,然后由VC來更新對應(yīng)的數(shù)據(jù)及視圖祈匙,此種方法可以忽刽,但是不推薦這樣做,因?yàn)閁ITableViewCell重用機(jī)制的原因夺欲。

4.2 如何更新對應(yīng)的數(shù)據(jù)模型跪帝?
4.2.1 標(biāo)準(zhǔn)的MVC寫法

根據(jù)蘋果官方的推薦,模型包含數(shù)據(jù)處理層些阅,包括網(wǎng)絡(luò)請求伞剑,數(shù)據(jù)加工及處理。

實(shí)例代碼如下

  • NewsModel.m (新聞模型對象市埋,里面封裝了點(diǎn)贊的網(wǎng)絡(luò)請求及數(shù)據(jù)處理)
/// 添加點(diǎn)贊
- (void)addLike:(void(^)(NSDictionary *json))callback {
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
    NSURLSessionTask *task = [session dataTaskWithURL:[NSURL URLWithString:API_GetGaoShiList] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (error == nil) {
                self.like = !self.like;
                if (self.like) {
                    self.likeNum += 1;
                } else {
                    self.likeNum -= 1;
                }
            }
            if (callback) {
                callback(nil);
            }
        });
    }];
    [task resume];
}

  • viewController.m (外界直接調(diào)用黎泣,監(jiān)聽回調(diào)block即可)
- (void)didTapNewsCellLike:(NewsModel *)newsModel {
    __weak typeof(NewsModel *) weakNewsModel = newsModel;
    [newsModel addLike:^(NSDictionary *json) {
        // 更新對應(yīng)的視圖
        [self updateNewsView:weakNewsModel];
    }];
}

運(yùn)行結(jié)果

1.gif

我們將點(diǎn)贊的網(wǎng)絡(luò)請求處理,數(shù)據(jù)處理都封裝到了模型里面缤谎,外界直接調(diào)用并監(jiān)聽結(jié)果的回調(diào)即可抒倚。

4.2.2 非標(biāo)準(zhǔn)的MVC寫法

有些小伙伴喜歡將網(wǎng)絡(luò)請求之間寫在VC里面,然后在VC里面處理請求和數(shù)據(jù)的處理坷澡。

實(shí)例代碼

  • ViewController.m (之間在VC中發(fā)網(wǎng)絡(luò)請求托呕,然后通過newId更新對應(yīng)的數(shù)據(jù)模型)
- (void)didTapNewsCellLike:(NewsModel *)newsModel {
     // 非標(biāo)準(zhǔn)的MVC寫法
    [self postLikeNetwork:newsModel];
}

#pragma mark - like network + data dealwith

- (void)postLikeNetwork:(NewsModel *)newsModel {
    NSString *api = @"http://rap2api.taobao.org/app/mock/163155/gaoshilist"; // 告示
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
    NSURLSessionTask *task = [session dataTaskWithURL:[NSURL URLWithString:api] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (error == nil) {
                [self dealwithLikeData:newsModel.newsId];
            }
        });
    }];
    [task resume];
}

- (void)dealwithLikeData:(NSString *)newsId {
    __block NewsModel *newsModel;
    [self.dataSource enumerateObjectsUsingBlock:^(NewsModel *obj, NSUInteger idx, BOOL *stop) {
        if ([obj.newsId isEqualToString:newsId]) {
            newsModel = obj;
            *stop = YES;
        }
    }];
    if (newsModel) {
        newsModel.like = !newsModel.like;
        if (newsModel.like) {
            newsModel.likeNum += 1;
        } else {
            newsModel.likeNum -= 1;
        }
        [self updateNewsView:newsModel];
    }
}

運(yùn)行結(jié)果

1.gif

這種方法也可以實(shí)現(xiàn)功能,而且不會(huì)出問題频敛,但是很明顯项郊,代碼行數(shù)增加了,并且會(huì)增加VC的臃腫斟赚,所以不是很推薦着降。

4.3 數(shù)據(jù)模型更新了后如何處理?

一般數(shù)據(jù)模型更新了后都需要更新對應(yīng)的視圖汁展,那是直接去更新視圖操作還是先通知VC鹊碍,然后讓VC去更新對應(yīng)的視圖。

4.3.1 標(biāo)準(zhǔn)的MVC寫法

蘋果官方推薦當(dāng)模型數(shù)據(jù)更新后告知VC食绿,方法主要有三種侈咕,分別是delegateblock和通知器紧。根據(jù)使用場景耀销,三種方法各有優(yōu)缺點(diǎn),本文以block為例講解铲汪。

使用場景:用戶更新點(diǎn)贊狀態(tài)后熊尉,需要告知后臺(tái)罐柳,然后更新對應(yīng)的模型數(shù)據(jù),然后在更新視圖狰住。

  • NewsModel.m (處理網(wǎng)絡(luò)請求张吉,更新數(shù)據(jù)然后回調(diào)給VC)
/// 添加點(diǎn)贊
- (void)addLike:(void(^)(NSDictionary *json))callback {
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
    NSURLSessionTask *task = [session dataTaskWithURL:[NSURL URLWithString:API_GetGaoShiList] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (error == nil) {
                self.like = !self.like;
                if (self.like) {
                    self.likeNum += 1;
                } else {
                    self.likeNum -= 1;
                }
            }
            if (callback) {
                callback(nil);
            }
        });
    }];
    [task resume];
}

viewController.m (在回調(diào)中更新視圖)

- (void)didTapNewsCellLike:(NewsModel *)newsModel {
    // 標(biāo)準(zhǔn)的MVC寫法
    __weak typeof(NewsModel *) weakNewsModel = newsModel;
    [newsModel addLike:^(NSDictionary *json) {
        // 更新對應(yīng)的視圖
        [self updateNewsView:weakNewsModel];
    }];
}

標(biāo)準(zhǔn)的MVC寫法是在模型對象內(nèi)部完成數(shù)據(jù)的處理,然后再告知VC催植。

4.3.1 非標(biāo)準(zhǔn)的MVC寫法

有些小伙伴喜歡在view視圖中直接對模型進(jìn)行操作肮蛹,數(shù)據(jù)的處理等操作。例子和之前的類似

  • NewsCell.m(當(dāng)用戶點(diǎn)贊后创南,直接在視圖中發(fā)起網(wǎng)絡(luò)請求伦忠,處理數(shù)據(jù),然后發(fā)通知更新對應(yīng)的視圖)
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        self.contentView.backgroundColor = [UIColor whiteColor];
        [self drawUI];
        [self addNotify];  // 監(jiān)聽通知
    }
    return self;
}

#pragma mark - notify

- (void)addNotify {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onNotifyModelUpdate:) name:kNotifyModelUpdate object:nil];
}

- (void)onNotifyModelUpdate:(NSNotification *)notify {
    NewsModel *model = (NewsModel *)notify.object;
    if (model == nil) {
        return;
    }
    if (![self.model.newsId isEqualToString:model.newsId]) {
        return;
    }
    // 更新視圖操作
    if (self.model.isLike) {
        [self.likeActionView updateImgName:@"like_red"];
    } else {
        [self.likeActionView updateImgName:@"like"];
    }
    [self.likeActionView updateTitle:[NSString stringWithFormat:@"%lu",(unsigned long)self.model.likeNum]];
}

// 用戶點(diǎn)擊了點(diǎn)贊按鈕
- (void)tapLike {
    /**
     * 數(shù)據(jù)模型更新了后如何處理稿辙?
     * 直接發(fā)通知,然后視圖監(jiān)聽通知并刷新視圖
     */
    __weak typeof(self) weakSelf = self;
    [self.model addLike:^(NSDictionary *json) {
        // 發(fā)通知
        [[NSNotificationCenter defaultCenter] postNotificationName:kNotifyModelUpdate object:weakSelf.model];
    }];
}

運(yùn)行結(jié)果

1.gif

結(jié)果運(yùn)行正常昆码,沒有任何問題,這種寫法就只是viewmodel之間進(jìn)行交互邻储,沒有涉及到vc赋咽,雖然可以實(shí)現(xiàn)功能,但是違背了MVC的設(shè)計(jì)初衷芥备,在視圖中做了很多不該視圖做的事情冬耿,而且代碼也比較多,復(fù)雜萌壳。

4.4 如何更新 view 上面的視圖元素

當(dāng)用戶點(diǎn)贊之后或者取消點(diǎn)贊視圖,需要將對應(yīng)的視圖圖標(biāo)替換日月,那當(dāng)數(shù)據(jù)更新后袱瓮,我們?nèi)绾胃耉iew上面的視圖元素呢?

4.4.1 標(biāo)準(zhǔn)的MVC寫法

由VC來更新對應(yīng)的視圖

  • viewController.m (在VC中更新視圖)
- (void)updateNewsView:(NewsModel *)newsModel {
    __block NSUInteger index = NSNotFound;
    [self.dataSource enumerateObjectsUsingBlock:^(NewsModel *obj, NSUInteger idx, BOOL *stop) {
        if ([newsModel.newsId isEqualToString:obj.newsId]) {
            index = idx;
            *stop = YES;
        }
    }];
    if (index == NSNotFound) {
        return;
    }
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
    [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}

通過newsId爱咬,找到該模型在數(shù)據(jù)源中的索引尺借,然后tableView更新對應(yīng)位置的``cell`即可。

4.4.1 非標(biāo)準(zhǔn)的MVC寫法

視圖中監(jiān)聽對應(yīng)通知精拟,然后更新對應(yīng)的視圖燎斩,前面例子已經(jīng)提到過了

  • NewsCell.m (直接在視圖中監(jiān)聽模型數(shù)據(jù)的變化,然后更新對應(yīng)視圖的元素)
#pragma mark - notify

- (void)addNotify {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onNotifyModelUpdate:) name:kNotifyModelUpdate object:nil];
}

- (void)onNotifyModelUpdate:(NSNotification *)notify {
    NewsModel *model = (NewsModel *)notify.object;
    if (model == nil) {
        return;
    }
    if (![self.model.newsId isEqualToString:model.newsId]) {
        return;
    }
    // 更新視圖操作
    if (self.model.isLike) {
        [self.likeActionView updateImgName:@"like_red"];
    } else {
        [self.likeActionView updateImgName:@"like"];
    }
    [self.likeActionView updateTitle:[NSString stringWithFormat:@"%lu",(unsigned long)self.model.likeNum]];
}

1.給cell添加監(jiān)聽通知蜂绎,當(dāng)模型數(shù)據(jù)發(fā)生變化后栅表,全局發(fā)通知,每一個(gè)cell監(jiān)聽到通知后都需要去做判斷师枣,即當(dāng)前cell對應(yīng)的model是否是需要更新的model怪瓶,通過newsId來區(qū)分。
2.很明顯践美,這種方法顯得比較low洗贰,而且每一個(gè)cell都要添加監(jiān)聽找岖,然后做判斷,性能較低敛滋。


本文參考
iOS 關(guān)于MVC和MVVM設(shè)計(jì)模式的那些事
雜談: MVC/MVP/MVVM


本文是我對MVC的一些理解及認(rèn)知许布,如果有問題歡迎提問,如有錯(cuò)誤绎晃,歡迎指正蜜唾,水平有限,犯錯(cuò)難免箕昭,不喜勿噴灵妨。本文為原著,如果轉(zhuǎn)載請注明出處落竹,謝謝泌霍。


項(xiàng)目連接地址 - MVC-Demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市述召,隨后出現(xiàn)的幾起案子朱转,更是在濱河造成了極大的恐慌,老刑警劉巖积暖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藤为,死亡現(xiàn)場離奇詭異,居然都是意外死亡夺刑,警方通過查閱死者的電腦和手機(jī)缅疟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遍愿,“玉大人存淫,你說我怎么就攤上這事≌犹睿” “怎么了桅咆?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長坞笙。 經(jīng)常有香客問我岩饼,道長,這世上最難降的妖魔是什么薛夜? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任籍茧,我火速辦了婚禮,結(jié)果婚禮上却邓,老公的妹妹穿的比我還像新娘硕糊。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布简十。 她就那樣靜靜地躺著檬某,像睡著了一般。 火紅的嫁衣襯著肌膚如雪螟蝙。 梳的紋絲不亂的頭發(fā)上恢恼,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音胰默,去河邊找鬼场斑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛牵署,可吹牛的內(nèi)容都是我干的漏隐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼奴迅,長吁一口氣:“原來是場噩夢啊……” “哼避归!你這毒婦竟也來了梢灭?” 一聲冷哼從身側(cè)響起磨隘,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤油挥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后暇检,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體产阱,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年块仆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了构蹬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悔据,死狀恐怖怎燥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蜜暑,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布策肝,位于F島的核電站肛捍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏之众。R本人自食惡果不足惜拙毫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望棺禾。 院中可真熱鬧缀蹄,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至衅码,卻和暖如春拯刁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背逝段。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工垛玻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奶躯。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓帚桩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親嘹黔。 傳聞我的和親對象是個(gè)殘疾皇子账嚎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容