ShannonChenCHN / iOS-App-Architecture

『iOS 应用架构研究』A journey of diving into iOS app architecture.
35 stars 9 forks source link

读『杂谈: MVC/MVP/MVVM』 #1

Open ShannonChenCHN opened 6 years ago

ShannonChenCHN commented 6 years ago

这篇文章按照架构的演进顺序,通过用三种不同的设计模式(MVC、MVP、MVVM)来实现一个示例场景,分别讲解了三种不同的设计模式各自的优缺点。

ShannonChenCHN commented 6 years ago

『杂谈: MVC/MVP/MVVM』

说明:下面两张图展示的是文章中用到的业务场景,图一是查看别人个人信息时的页面展示,图二是查看自己的个人信息的页面展示

2595746-29036860ea42fbf8

2595746-f98abdd550485408

一、MVC

1. 概念

2. 现状:MVC 之消失的 C 层

2595746-e8770c633cb5cc74

3. 正确的 MVC 使用姿势

误区:ViewController 就一定是 C 层?

正确的MVC应该是这个样子的: 2595746-2d7ea66f64955f87

4. “新 MVC ” 解决了什么问题

5. MVC 仍然存在的问题

二、MVP

1. 简介

2. 案例分析

用 MVP 来实现前面提到的业务场景时,架构应该是这样的:

2595746-e8bf88209857beeb
@interface VoiceCell ()
@end
@implementation VoiceCell

- (void)setPresenter:(VoiceCellPresenter *)presenter {
    _presenter = presenter;

    if (!presenter.didUpdatePlayStateHandler) {
        __weak typeof(self) weakSelf = self;
        [presenter setDidUpdatePlayStateHandler:^(NSUInteger playState) {
            switch (playState) {
                case PlayStateBuffering: weakSelf.playButton... break;
                case PlayStatePlaying: weakSelf.playButton... break;
                case PlayStatePaused: weakSelf.playButton... break;
            }
        }];
    }
}
typedef void(^PlayCompletion)(NSError *error, id result);

typedef NS_ENUM(NSInteger, PlayState) {
    PlayStateBuffering,
    PlayStatePlaying,
    PlayStatePaused,
    PlayStateEnded,
};

@interface VoiceCellPresenter : NSObject

@property (copy, nonatomic) void(^didUpdatePlayStateHandler)(NSUInteger);

- (NSURL *)playURL;
- (void)playWithCompletionHandler:(PlayCompletion)completion;

@end

@implementation VoiceCellPresenter

- (void)playWithCompletionHandler:(PlayCompletion)comletion {
     ....
     // 播放完成后,调用 didUpdatePlayStateHandler
     if (self.didUpdatePlayStateHandler) {
          self.didUpdatePlayStateHandler(PlayStateEnded);
     }
}

@end
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    BlogViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ReuseIdentifier];
    cell.presenter = self.presenter.allDatas[indexPath.row];

    __weak typeof(self) weakSelf = self;
    __weak typeof(cell) weakCell = cell;
    __block PlayCompletion playCompletion = nil;
    __block __weak PlayCompletion weakCompletion = playCompletion;
    playCompletion = ^(NSError *error, id result) {
                 // 找到下一个 cell 的 presenter
                 if (weakSelf.presenter.allDatas.count > indexPath.row + 1) {
                      BlogCellPresenter *nextPresenter = weakSelf.presenter.allDatas[indexPath.row + 1];
                      [nextPresenter  playWithCompletionHandler: weakCompletion];
                  }
        };

    [cell setDidPlayHandler:^{        
        [weakCell.presenter playWithCompletionHandler:playCompletion];
    }];
    return cell;
}

3. 小结

MVP 的主要思想是把业务展示和业务逻辑分离开来,Presenter 层专门负责业务逻辑,View 层只管展示,Controller 将 Presenter 层和 View 层绑定起来,进行数据和事件通信。

三、MVVM

1. MVP 模式中仍然存在的问题

在 V 层和 P 层之间进行无耦合的通信一般有两种方式:delegate 或者 block,也就是说,如果想要在 P 层更新数据后,通知 V 层更新的话,是绕不过这两种方式的。这两种方式可以将 V 层和 P 层隔离开,以实现解耦。

这有什么问题吗?问题就在于为了让 V 跟着数据的状态改变一起变化,我们需要写很多“通信”代码,要么是定义 block 属性,要么是定义 delegate 方法,当 V 层的监听的数据越多,就要定义越多的属性或方法。

2. MVVM

除了 delegate 和 block 这两种通信方式之外,还有一种低耦合的通信方式就是 KVO。通过 KVO 的使用,我们可以省去很多胶水代码。

MVVM 与 MVP 相比,基本思想还是差不多的——将业务逻辑从 C 层中分离出来,只是,MVVM 相比 MVP 来讲最主要的特点就在于使用了数据绑定。所以,我们可以说,MVVM 其实只是 MVP 的绑定进化体,除去数据绑定方式,其他的和 MVP 如出一辙,只是可能呈现方式是Command/Signal 而不是 CompletionHandler 之类的。(???)

当然实现数据绑定的方式有很多种,除了 Apple 官方提供的 KVO 之外,比较广为人知的就是 ReactiveCocoa(简称 RAC) 了。

四、总结

五、遗留问题

六、收获