• iOS的MVP设计模式


    前言

    最近一段时间,公司刚做完一个MVP项目,我有一个习惯:在项目结项之后总结一下项目中新接触的问题。Google一下关键字“iOS MVP”,发现一些文章,最后是 这篇文章 带给我对MVP 的一些认识。MVP似乎有好多的变种,作者所说的这种有如下特点(以下是原文摘抄一段):

    The characteristics of this variant are:

    *   the view part of the MVP consists of both UIViews and UIViewController

    *   the view delegates user interactions to the presenter

    *   the presenter contains the logic to handle user interactions

    *   the presenter communicates with model layer, converts the data to UI friendly format, and updates the view

    *   the presenter has no dependencies to UIKit

    *   the view is passiv (dump)

    利用有道翻译如下:

    • MVP的 V 层是由UIViewController 和UIView 共同组成
    • view 将委托presenter 对它自己的操作,(简单来说就是presenter发命令来控制view的交互,要你隐藏就隐藏,叫你show 你就乖乖的show)
    • presenter拥有对 view交互的逻辑(就是上面说的意思)
    • presenter跟model层通信,并将数据转化成对适应UI的数据并更新view
    • presenter不需要依赖UIKit
    • view层是单一,因为它是被动接受命令,没有主动能力
     
     

    presenter 作为业务逻辑的处理者,首先要向Service层拿数据赋值给model,所以它将可以向model层通信。其次,UI的处理权移交给了它,所以它需要与view成通讯,发送命令更新UI。同时,UI的响应将触发业务逻辑的处理,所以view 层向presenter层通讯,告诉他用户做了什么操作,需要你反馈对应的数据来更新UI。这样就完成了从用户交互获得交互反馈到整个业务逻辑。

    示例代码如下:

    首先看一下demo结构

     
     

    UserViewProtocol 协议定义了一下方法,这些方法其实就是presenter对view层发送的命令:

    /**

     * UserViewProtocol 协议定义了一下方法,这些方法其实就是presenter对view层发送的命令

     */

    #import "UserModel.h"

    @protocol UserViewProtocol <NSObject>

    - (void)userViewDataSource:(NSArray<UserModel *> *)data;

    - (void)showIndicator;

    - (void)hideIndicator;

    - (void)showEmptyView;

    @end

    UserService 类是用来请求数据给presenter的,我就只定义了一个方法:

    - (void)getUserInfosSuccess:(SuccessHandler)success andFail:(FailHandler)fail;

    这一层,其实也可以很复杂,这就涉及网络层的架构了,具体可以看一下网络层架构方面的博客或书籍 。

    ViewController类则是UI层,它实现了tableview自己的协议,还实现了用户交互的协议 UserViewProtocol,也就说,presenter向UI层发送命令,其实是发给UI层的viewController,实际上是控制器来被动的更新UI。这个不管是MVC还是MVP,view的实际控制权应该都是viewController。

    协议中的几个方法实现如下:

    #pragma mark - UserViewProtocol

    - (void)userViewDataSource:(NSArray<UserModel *> *)data {

        self.friendlyUIData = data;

        [self.tableView reloadData];

    }

    - (void)showIndicator {

        [self.indicatorView startAnimating];

        self.indicatorView.hidden = NO;

    }

    - (void)hideIndicator {

        [self.indicatorView stopAnimating];

    }

    - (void)showEmptyView {

        UIAlertController *alertView = [UIAlertController alertControllerWithTitle:@"Alert"message:@"show empty view" preferredStyle:UIAlertControllerStyleAlert];

        [alertView addAction:[UIAlertAction actionWithTitle:@"确定"style:UIAlertActionStyleDefault handler:**nil**]];

        [self presentViewController:alertView animated:YES completion:^{

        }];

    }

    另外,在viewDidload中,调用了presenter的两个public 方法,示例代码如下:

    [self.presenter fetchData];

    - (Presenter *)presenter {

        if (_presenter == nil) {

            _presenter = [[Presenter alloc] init];

            [_presenter attachView:self];

        }

        return _presenter;

    }

    下面来看看Presenter类:

    先说attachView 方法,这个方法是对外公开的,目的就是为了将实现了UserViewProtocol 协议的对象(其实应该就是控制器,因为view的直接操作者就是view Controller)绑定到presenter 上,说白了就是presenter 可以直接拿到实现了UserViewProtocol 协议的对象,并且向他发送命令(协议实现的方法,前面有说到),具体该方法的实现:

    #import <Foundation/Foundation.h>

    #import "UserViewProtocol.h"

    #import "UserModel.h"

    NS_ASSUME_NONNULL_BEGIN

    @interface Presenter : NSObject

    /**

     将一个实现了 UserViewProtocol 协议的对象绑定到 presenter上来

     @param view 实现了UserViewProtocol的对象,一般来说,应该就是控制器,在MVP中,V 和 VC 共同组成UI 层

     */

    - (void)attachView:(id <UserViewProtocol>)view;

    /**

     这个是对外的入口,通过这个入口,实现多个对内部的操作,外部只要调用这个接口就可以了

     */

    - (void)fetchData;

    @end

    NS_ASSUME_NONNULL_END

    注意attachView属性其实就是我们平时使用委托协议( delegate),只是名字换成了attachView 而已,因为presenter和 viewController相互持有,所以必须要通过weak 来打破循环引用,所以必须要用weak修饰。

    再来说fetchData方法:

    公开这个方法,只是为了数据请求有个统一的接口,而不需要presenter分开多次调用,presenter自己处理所有事情,不让viewController参和进来,具体实现就看demo。

    最后说一下presenter的私有方法:

    /**

     如果数据比较复杂,或者UI渲染的数据只是其中很少一部分,将原数据处理,输出成UI渲染的数据。(题外话:这里其实还可以使用协议,提供不同的数据格式输出。)

     @param originData 原始数据

     @return 将原始数据转换为UI需要数据

     */

    - (NSArray<UserModel *> *)processOriginDataToUIFriendlyData:(NSArray *)originData {

        NSMutableArray<UserModel *> *friendlyUIData = [NSMutableArray array];

        for (NSDictionary *dic in originData) {

            if ([[dic valueForKey:@"gender"] isEqualToString:@"male"]) {

                UserModel *model = [UserModel userWithDict:dic];

                [friendlyUIData addObject:model];

            }

        }

        return friendlyUIData;

    }

    这个私有方法是将原始数据转换成UI所需要的数据,这样UI拿到数据就可以直接使用,而不用做各种判断,逻辑依然放在了presenter中。而且,这个数据处理可以做成协议,输出不同UI需要的数据,这个也可以看关于view架构的文章。

    demo地址

     
  • 相关阅读:
    一些常用的代码规范
    策略模式学习,使用go实现策略模式
    数据库作业3:第二章课后题
    数据库作业4:SQL练习1
    对数据库相关的概念自己的理解
    SQL server的初步使用
    对于高德地图SDK的开发使用
    滑稽(好像会动)!
    羊车门作业
    对Python课的看法
  • 原文地址:https://www.cnblogs.com/MrXie821385843/p/10331135.html
Copyright © 2020-2023  润新知