MVVM我的实践(二)

【ViewModel、AppService和DomainService怎么分工协作】

前面说到ViewModel把很多事情代理给了AppService去做,其实还有另外一部分事情它代理给DomainService去做了,这里说的AppService和DomainService不一定是一个具体的类,它们可以是几个这种类型的类,或者它们可以是这种类型的类的协议,而不是具体实现类。这里我没有说应该怎么怎么做,因为我在写ViewModel的过程中也存在这些类的各种组合情况,没有固定的模式。不过,在我慢慢探索的过程中,我还是发现了可能遵循以下原则会比较好:

  1. 依赖协议而不是依赖实现。
  2. 要分层,每层只做自己该做的事。
  3. 尽量不要跨层交互。

分层的好处是明显的,如果分层的职责分配的合理,分析代码时会很清晰,哪一层的代码问题去哪一层找;有利于隔离变化,保持代码最大程度的重用,当移植时,变化到哪一层就替换哪一层,其他不变的层便是可以重用的。
依赖协议而不依赖实现便是一种分层所仰赖的重要技术,有了它分层才能实现替换变化的代码,保持不变的代码被重用。
每层只做自己该做的事,尽量不要跨层交互,就是分层的重要基础,没有按照这个原则去分层,那么也不可能实现恰当的隔离变化层次用于代替,保持其他部分层次的稳定重用。

下面用“MVVM我的实践(一)”里面提到的小群组主页ViewModel示例来讲解Controller/View,ViewModel,AppService和DomainService这些类之间怎么形成分层,去分工协作做好自己分内的事情。代码只展示示意性的代码,有删除其他代码,同时有的代码我也修改了,不是项目原来的代码,因为原来的代码有很多写得不好,无法表达出我要表达的东西。

Controller/View代表的显示层的职责:
作为无脑的显示层,它们只需要拿到数据,不做判断,只做直接呈现。就行下面的分页菜单self.slideMenu,它只是针对ViewModel在用到它刷新时,去执行对每个菜单项,绘制不同类型的badge图标,至于为什么是这个菜单项,为什么是这种类型badge,它不关心。

- (GroupMainViewModel *)viewModel {
///  省略了前面的代码
        @weakify(self);
        [_viewModel hasLargeNumNewMessage:^() {
            @strongify(self);
            [self.slideMenu showMoreBadgeForItem:0];
        }];
        [_viewModel hasNormalNumNewMessage:^(NSUInteger num) {
            @strongify(self);
            [self.slideMenu showBadgeText:[NSString stringWithFormat:@"%@",@(num)] forItem:0];
        }];
        [_viewModel hasNewMessage:^{
            @strongify(self);
            [self.slideMenu showDotBadgeForItem:0];
        }];
        [_viewModel hasNewDynamic:^{
            @strongify(self);
            [self.slideMenu showDotBadgeForItem:1];
        }];
        [_viewModel hasNewActivity:^{
            @strongify(self);
            [self.slideMenu showDotBadgeForItem:2];
        }];
///  省略了后面的代码
}

ViewModel代表的大脑层(控制层)(中枢层)的职责:

  1. 接受表现层(Controller/View)提供的信息,改变自己的状态,反馈给表现层。
  2. 接受表现层传递的交互,执行对交互的响应命令,改变自己的状态,反馈给表现层。

直接通过属性从Controller接受信息:

        _viewModel = [[GroupMainViewModel alloc] initWithAppService:self.appService];
        _viewModel.groupId = self.groupId;
        _viewModel.groupStatus = self.groupStatus;
        _viewModel.sessionId = self.sessionId;
        _viewModel.adminUserId = self.adminUserId;
        _viewModel.unreadChatMessageCount = self.chatUnreadCount;
        _viewModel.currentPageIndex = self.slideMenu.currentPageIndex;

直接代劳处理Controller内的交互事件:

    // 动态
    GroupDynamicListViewController *dynamicVC = [[GroupDynamicListViewController alloc] init];
    dynamicVC.title = @"动态";
    dynamicVC.groupId = self.viewModel.groupId;
    dynamicVC.JoinGroupBlock = ^{
        @strongify(self);
        [self.viewModel joinGroup:self.viewModel.groupId];
    };
    dynamicVC.MaxIdBlock = ^(NSInteger maxId){
        @strongify(self);
        if (self.viewModel.currentPageIndex == 1) {
            [self.viewModel cacheMaxGroupDynamicId:maxId];
        }
    };
    

通过对API数据和缓存数据的对比分析,决定要不要显示badge,对哪个菜单,显示什么badge。但是它并不直接获取API数据和缓存数据,它只是在需要它们时通过外界传入自身的self.appService来取得他要的东西。那么为什么不让它管这两个任务而要代理给别的类完成,我的考虑有几点:一,获取API数据、获取缓存数据,这两种功能很容易让人想到要封装成单独的组件,这样在别的地方也能够重用它们了;二,这两种功能实现起来估计要依赖不少其他类,而我觉得ViewModel不应该依赖那么多其他那些与它的逻辑任务不直接相关的类,而这里ViewModel的任务只是利用这些数据做逻辑判断,而不管数据的获取过程;三,尤为重要的是,当要在别的地方重用这个ViewModel时,这两个获取数据的方式很有可能就不一样了,我希望到时候能够轻易替换它们的实现,而又不需要更改或者大改ViewModel原有的代码。

ViewModel的状态确定了之后,得让表现层对其状态做出反馈,这里剩余的跟UI有关的事情,它就通过block代理出去给Controller去做了,在这个代理的过程中,它也已经帮Controller把任务精细化了,这里它并没有通过showDotForIndex(NSInteger index)这种带参的block将事件丢给Controller,因为那样的话Controller还得对index做判断,以确定到底最终要给哪个菜单显示badge,而是直接通过
-(void)hasNewDynamic:(update)update;这类的命名很明确的方法,让Controller知道要从这里设置回调block来针对Dynamic的菜单要做badge更新。这里的命名比较讲究,方法不要这样命名-(void)showDotBadgeForSecondIndex:(update)update;因为以后可能Dynamic菜单不是放在second Index位置了,显示badge的方式也不是dot的方式了,那这个方法名字就会给人误导、迷惑。所以为了保证ViewModel在处理这种变化方便的通用性,方法的命名不要与其最终的实现有太大的关联。那么,说到通用性,这种方法-(void)hasNewDynamic:(update)update;也给人一种很专用的感觉啊,毕竟以后这个位置的菜单不是Dynamic菜单,而是别的菜单了呢?只能说,在这种业务层次来讲,这里的这个ViewModel就是一个专用的ViewModel了,它只适合有Dynamic菜单的这个业务模块使用,不适合换成别的菜单的别的业务模块,所以像这样专门性的、意义明确的方法是适合它的。很多时候,我发觉要把ViewModel的通用性设计好很不容易,你不能把它做得太通用了,否则用到它的地方得做很多专用性的判断,就比如暴露showDotForIndex(NSInteger index)这样的block,那么每次用到它时,使用方都得做index的判断,这样的话我认为ViewModel并没有实现易用性这个目标,因为它针对性不强。你也不能把它做得太专用了,就比如暴露-(void)showDotBadgeForSecondIndex:(update)update;这样的接口,它的针对性就太强了,连怎么实现UI效果都给你指定了,这就超出它的职责了。所以,针对这个例子里面的ViewModel我的设计原则是,一,要让它有针对性地服务于它所属的模块,暴露的接口要能够见名知意,而且尽量不让使用方要对type、index之类的参数做判断;二,不该它管的事情它不管,接口命名要彰显业务意义大于操作意义。


- (void)loadData{
    @weakify(self);
    [self.appService loadDataCompletion:^{
        @strongify(self);
        [self showBadgeWithApiMaxGroupDynamicId:[self.appService apiMaxGroupDynamicId]
                          apiMaxGroupActivityId:[self.appService apiMaxGroupActivityId]
                        cachedMaxGroupDynamicId:[self.appService cachedMaxGroupDynamicId]
                       cachedMaxGroupActivityId:[self.appService cachedMaxGroupActivityId]];
    }];
}

- (void)showBadgeWithApiMaxGroupDynamicId:(NSInteger)apiMaxGroupDynamicId
                    apiMaxGroupActivityId:(NSInteger)apiMaxGroupActivityId
                  cachedMaxGroupDynamicId:(NSInteger)cachedMaxGroupDynamicId
                 cachedMaxGroupActivityId:(NSInteger)cachedMaxGroupActivityId{
    if ([self shouldShowUnreadBadgeWithCacheNum:cachedMaxGroupDynamicId apiNum:apiMaxGroupDynamicId]) {
        [self showDotBadgeForDynamic];
    }else{
        [self showNoneBadgeForDynamic];
    }
    if ([self shouldShowUnreadBadgeWithCacheNum:cachedMaxGroupActivityId apiNum:apiMaxGroupActivityId]) {
        [self showDotBadgeForActivity];
    }else{
        [self showNoneBadgeForActivity];
    }
}

- (void)showDotBadgeForDynamic{
    if (self.showDotForDynamicBlock) {
        self.showDotForDynamicBlock();
    }
}

- (void)showDotBadgeForActivity{
    if (self.showDotForActivityBlock) {
        self.showDotForActivityBlock();
    }
}

- (void)showNoneBadgeForDynamic{
    if(self.clearBadgeForDynamicBlock){
        self.clearBadgeForDynamicBlock();
    };
}

- (void)showNoneBadgeForActivity{
    if (self.clearBadgeForActivityBlock) {
        self.clearBadgeForActivityBlock();
    }
}

最后形成自身状态的改变,反馈给表现层去刷新UI。通过精细、单一任务的接口设计,Controller很容易知道针对什么状态,去什么方法去设置响应的回调block。

@weakify(self);
        [_viewModel hasLargeNumNewMessage:^() {
            @strongify(self);
            [self.slideMenu showMoreBadgeForItem:0];
        }];
        [_viewModel hasNormalNumNewMessage:^(NSUInteger num) {
            @strongify(self);
            [self.slideMenu showBadgeText:[NSString stringWithFormat:@"%@",@(num)] forItem:0];
        }];
        [_viewModel hasNewMessage:^{
            @strongify(self);
            [self.slideMenu showDotBadgeForItem:0];
        }];
        [_viewModel hasNewDynamic:^{
            @strongify(self);
            [self.slideMenu showDotBadgeForItem:1];
        }];
        [_viewModel hasNewActivity:^{
            @strongify(self);
            [self.slideMenu showDotBadgeForItem:2];
        }];

AppService代表的执行层(干杂活的)的职责
AppService对于ViewModel是一个服务层,这个层可以由一个或多个AppService的相关类、对象组成,只要ViewModel有什么事情不能直接做的,就交给它们做。只要一个ViewModel对Controller扬言要接管一整个模块功能或一整个流程的运作,那么它要处理的事情就是多而杂的。就这个例子而言,ViewModel要做完整个模块的事情,起码要涉及好几项它不应该直接去参与的事情,一,API数据请求;二,数据缓存的读写;三,请求成功与失败的弹窗提示。
在ViewModel该如何去做那些不应该直接由它做的事情的问题上有几个可以实现的方案。
一,ViewModel作为功能实现类直接去做那些事。肯定不建议这样。那不是它该做的。
二,ViewModel让其他功能实现类去做那些事。也不建议这样。比如如果实现类是一个View的类,那样ViewModel就直接关联View了。
三,ViewModel用一些AppService实现类(通常继承自NSObject)去操控那些功能实现类完成那些事情,ViewModel只若引用这些AppService实现类。这种方案可以避免ViewModel有可能直接强引用到View。但是这样一来,ViewModel还是会有依赖多个类。
四,ViewModel只若引用一个AppService实现类,所有不属于它做的事情,都代理给这个AppService类去做。这种方案可以避免ViewModel依赖多个类。但是当要移植ViewModel时,这个AppService实现类可能不适用了,要更换,这样的话会导致ViewModel代码的修改。
五,ViewModel只若引用一个AppService的协议,当ViewModel要移植它处使用时,AppService协议跟着移植,只需要替换AppService协议的实现类。这种方案可以让ViewModel能够不受AppService实现类更换的影响,达到隔离变化,稳定可重用代码的效果。
以上三到五的方案我都用过,应该说很多情况来看五是最好的。不过五的方案会导致这个唯一的AppService变得要做的事情很庞杂,什么都管,感觉职责很模糊。这种时候我觉得可以通过适当的继承和组合技术,来给它瘦身,让它变得功能更聚焦、职责更清晰。比如很多列表类ViewModel的AppService实现类都需要处理请求错误提示、无数据提示这样的功能,那么就创建一个ListBaseAppServiceImpl这样AppService实现类的基类,它里面包含了对这两种功能的服务的实现,这个基类的子类就可以只关注它们所对应功能模块的特别的服务的实现即可。又比如,有的服务类需要用到缓存服务,而我们又不想把缓存服务的接口由这个服务实现类来暴露的话,就可以专门创建一个CacheAppService的缓存协议,把缓存协议实现类这作为它的一个属性,或者让它通过一个方法返回缓存协议的实现类,再在这个协议里面暴露缓存服务的接口。
回到小群组主页ViewModel这个例子,AppService层提供了ViewModel所需的所有数据,以及这些数据的请求和缓存操作支持,它让ViewModel像个战场上的指挥官一样,不必亲自去获取情报、不必亲自去上战场搏斗,就能够轻松地指挥作战,谋划战局;它又像一个管家,而ViewModel像个房子主人一样,主人只需要关注如何装点房子,至于需要用到什么材料、饰品,只需要吩咐管家一声就能得到,要摆放什么物体,修改什么墙面,只需要吩咐管家一声,管家就会帮做好:

#import "GroupMainDomainService.h"
#import "CacheAppService.h"

@protocol GroupMainAppService 

- (instancetype)initWithDomainService:(id)service
                      cacheService:(id)cacheService;
- (void)loadDataCompletion:(void(^)())completion;
- (void)exeCacheNewApiMaxGroupDynamicId;
- (void)exeCacheNewApiMaxGroupActivityId;
- (void)exeCacheListReadMaxGroupDynamicId:(NSInteger)dynamicId;
- (void)exeCacheListReadMaxGroupActivityId:(NSInteger)activityId;
- (void)exeCleanGroupMainCacheForCurrentUser;
- (NSInteger)cachedMaxGroupDynamicId;
- (NSInteger)cachedMaxGroupActivityId;
- (NSInteger)apiMaxGroupDynamicId;
- (NSInteger)apiMaxGroupActivityId;

@end


#import "GroupMainAppServiceImpl.h"
#import "GroupMainDomainService.h"
#import "PopDialogAppService.h"
#import "CacheAppServiceImpl.h"

@interface GroupMainAppServiceImpl(){
    NSInteger _apiMaxGroupDynamicId;
    NSInteger _apiMaxGroupActivityId;
}

@property (nonatomic, weak) iddomainService;
@property (nonatomic, weak) idcacheService;
@property (nonatomic, assign) NSInteger userId;

@end

@implementation GroupMainAppServiceImpl

- (instancetype)initWithDomainService:(id)service cacheService:(id)cacheService{
    if (self = [super init]) {
        NSAssert(service, @"domain service 为空");
        self.domainService = service;
        self.cacheService = cacheService;
        self.userId = [sharedDelegate userEntity].userId;
    }
    return self;
}

- (void)loadDataCompletion:(void (^)())completion{
    //@weakify(self);
    [self.domainService exeGetMaxDynamicActivityIdSuccess:^(NSInteger maxDynamicId, NSInteger maxActivityId) {
        //@strongify(self);
        _apiMaxGroupDynamicId = maxDynamicId;
        _apiMaxGroupActivityId = maxActivityId;
        if (completion) {
            completion();
        }
    } failure:^(NSString *errorMsg) {
        [[PopDialogAppService sharedInstance] showError:errorMsg];
    }];
}

- (void)exeCacheNewApiMaxGroupDynamicId{
    if (_apiMaxGroupDynamicId != NSNotFound && _apiMaxGroupDynamicId > 0) {
        if ([self.domainService teamId] != 0) {
            [[self.cacheService getGroupMainCacheService] saveMaxDynamicId:_apiMaxGroupDynamicId forTeam:[self.domainService teamId]];
        }
        if ([self.domainService groupId] != 0) {
            [[self.cacheService getGroupMainCacheService] saveMaxDynamicId:_apiMaxGroupDynamicId forGroup:[self.domainService groupId]];
        }
    }
}

- (void)exeCacheNewApiMaxGroupActivityId{
    if (_apiMaxGroupActivityId != NSNotFound && _apiMaxGroupActivityId > 0) {
        if ([self.domainService teamId] != 0) {
            [[self.cacheService getGroupMainCacheService] saveMaxActivityId:_apiMaxGroupActivityId forTeam:[self.domainService teamId]];
        }
        if ([self.domainService groupId] != 0) {
            [[self.cacheService getGroupMainCacheService] saveMaxActivityId:_apiMaxGroupActivityId forGroup:[self.domainService groupId]];
        }
    }
}

- (void)exeCacheListReadMaxGroupDynamicId:(NSInteger)dynamicId{
    if (dynamicId <= [self cachedMaxGroupDynamicId]) {
        return;
    }
    _apiMaxGroupDynamicId = dynamicId;
    [self exeCacheNewApiMaxGroupDynamicId];
}

- (void)exeCacheListReadMaxGroupActivityId:(NSInteger)activityId{
    if (activityId <= [self cachedMaxGroupActivityId]) {
        return;
    }
    _apiMaxGroupActivityId = activityId;
    [self exeCacheNewApiMaxGroupActivityId];
}

- (void)exeCleanGroupMainCacheForCurrentUser{
    [[self.cacheService getGroupMainCacheService] cleanGroupMainCacheForCurrentUser];
}

- (NSInteger)apiMaxGroupDynamicId{
    return _apiMaxGroupDynamicId;
}

- (NSInteger)apiMaxGroupActivityId{
    return _apiMaxGroupActivityId;
}

- (NSInteger)cachedMaxGroupDynamicId{
    if ([self.domainService teamId] != 0) {
        return [[self.cacheService getGroupMainCacheService] cachedMaxDynamicIdForTeam:[self.domainService teamId]];
    }else{
        return [[self.cacheService getGroupMainCacheService] cachedMaxDynamicIdForGroup:[self.domainService groupId]];
    }
}

- (NSInteger)cachedMaxGroupActivityId{
    if ([self.domainService teamId] != 0) {
        return [[self.cacheService getGroupMainCacheService] cachedMaxActivityIdForTeam:[self.domainService teamId]];
    }else{
        return [[self.cacheService getGroupMainCacheService] cachedMaxActivityIdForGroup:[self.domainService groupId]];
    }
}

@end

DomainService代表的API服务层的职责
如果ViewModel绕过AppService而直接使用DomainService,那么DomainService对ViewModel而言就是一个API服务层,专职为ViewModel获取服务端数据。如果ViewModel只与AppService打交道,那么DomainService就是AppService的API服务层,专职为AppService获取服务端数据。专门设置DomainService作为一层意义是明显的,毕竟移动APP主要依赖的就是API,不同的APP依赖不同的API,而不同的APP有可能有相同可共用的ViewModel,因为有不少APP逻辑是通用的,这时候,为了让ViewModel可重用,API可替换,就要让用到API的地方(这里是ViewModel或者AppService)依赖一个DomainService层,而且是依赖协议而非实现类,这样只要协议不变,基于协议的上层代码就可以不变,只需要更换作为实现的DomainServiceImpl类即可。

下面是DomainService协议:

#import 

@protocol GroupMainDomainService 

- (instancetype)initWithTeamId:(NSInteger)teamId;
- (instancetype)initWithGroupId:(NSInteger)groupId;
- (NSInteger)teamId;
- (NSInteger)groupId;
- (void)exeGetMaxDynamicActivityIdSuccess:(void(^)(NSInteger maxDynamicId, NSInteger maxActivityId))success
                                   failure:(void(^)(NSString *errorMsg))failure;

@end

小结:
如果用一个比喻来阐述Controller、View、ViewModel、AppService、DomainService的关系,我觉得Controller就像一间商店,View是商店里面的商品,ViewModel是店主,AppService是打杂的,DomainService是拉货的。商店存放着商品,驻守着店长、打杂员工、拉货司机。顾客进入一家店后,店主便承担起了接待的任务,为了服务好顾客,商品怎么摆、怎么用由他说了算,打杂员工做什么事情也由他安排,拉货司机要去补什么货也由他来指挥。顾客在这家店逛完了,他可以让打杂的带顾客去逛另由另一店主管的另一家店,如果顾客要去的下一家店还由他来管,他就直接和顾客一起由打杂员工送他们光临下一家店。总之,在所有流程里面,店主都起着决策和指挥的左右,其他人员服从管理去执行具体任务,商店是被支配的场所,它有时候是人员的归属地有时候只是人员的落脚点,商品是被使用的物件。这些角色所做的所有事情都为了接待好顾客,让他们有一个良好的观光、游玩或购物体验。

【继续说说依赖协议编程】

我在项目里这方面比较典型的一个应用是用在处理列表性API的数据请求到数据展示这一套流程上面,因为我发现,每次我写列表API的请求逻辑的时候,按照以往的做法,总需要在API的blocks里面重新写一套结果数据封装、界面刷新、请求结果提示、无数据提示等各种逻辑。本来就是有点复杂,又重复的东西,还每次都得根据不同接口写一遍,不仅觉得没必要,还容易出错。那么好的做法当然就是想办法让通用的代码只需费心写好一次,后面在其他地方轻松调用,再配合需要变动的部分,就能实现一个新API,新模块的完整功能。
要实现这个目标,首先得提取出那些不变的逻辑代码,用基类和协议把它们固话下来,而变化的部分就要能够通过子类和实现类来实现定制和替换。在这个设计里面,不变的部分是数据表格的分页刷新逻辑、数据接口的分页加载逻辑、界面和API的数据传递逻辑、接口的数据解析和封装逻辑、界面的结果状态显示逻辑等;变动的部分是界面结果状态提示语、数据表格Cell的样式、界面给到API的参数、API返回结果的字段、API数据结果的封装等。
一,封装表格的通用逻辑
项目里但凡用到表格的地方有很多逻辑都是一样的,一样的表格初始化方式、一样要实现那几个常用的代理,所以我把这两部分的逻辑都放到了一个基类TableViewManager里面:

/**
 *  初始化表格
 */
- (void)setupTableView {
    // 表格
    self.theTableView = [[UITableView alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 0.0) style:UITableViewStylePlain];
    self.theTableView.scrollsToTop = YES;
    self.theTableView.backgroundColor = [UIColor clearColor];
    self.theTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    self.theTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.theTableView.dataSource = self;
    self.theTableView.delegate = self;
}

- (void)showTableInView:(UIView *)view{
    if (!view || !self.theTableView) {
        return;
    }
    self.theTableView.frame = view.bounds;
    [view addSubview:self.theTableView];
}

#pragma mark - UITableView Delegate & DataSource

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

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

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 44.0;
}

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

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
    }
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    //消除cell选择痕迹
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

当用在不同的模块时,只需要子类话这个基类,重写几个属性就行,比如HouseSourceArticleViewManager:

#pragma mark - UITableView Delegate & DataSource

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    ArticleCellFrame *frame = self.listDatas[indexPath.row];
    return frame.cellHeight;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellIdentifier = @"ArticleCellIdentifier";
    ArticleResultCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (!cell) {
        cell = [[ArticleResultCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
        cell.selectedBackgroundView = [[UIView alloc] initWithFrame:cell.frame];
        cell.selectedBackgroundView.backgroundColor = [UIColor colorWithNumber:28];
    }
    [self configureCell:cell atIndexPath:indexPath];
    
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [super tableView:tableView didSelectRowAtIndexPath:indexPath];
    //[self readToutiaoArticleInCell:indexPath];
    ArticleCellFrame *frame = self.listDatas[indexPath.row];
    if (frame.flagType == 3) {//广告
        BrowserViewController *browserVC = [[BrowserViewController alloc] init];
        [browserVC handleUrlString:frame.articleViewModel.articleUrl navigationVC:self.navVC];
    }else{
        ArticleBrowseViewController *browseVC = [[ArticleBrowseViewController alloc] initWithArticleId:frame.articleViewModel.articleId articleUrl:frame.articleViewModel.articleUrl];
        [self.navVC pushViewController:browseVC animated:YES];
    }
}

这个基类可以让要使用表格的控制器很轻易就拥有了使用表格的能力,只需要简单初始化:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.viewManager = [[HouseSourceArticleViewManager alloc] init];
    [self.viewManager showTableInView:self.view];
}

二,固化上层与数据请求层的交互中介逻辑
这里的上层指TableViewManager基类,数据请求层指DomainService协议(下面会说到)的实现类。这里的交互中介固定逻辑是指上层需要往交互中介传什么参数给数据请求层;数据请求层需要把什么结果通过交互中介返回给上层。上层需要传递的固定参数是请求的页码序号,数据请求层需要返回给上层的数据固定有三个,每页数据量、数据列表(可以为空)、错误信息(请求错误时就会非空),为了让上层能够针对数据请求层的各种状态做好相应的响应,我还让数据请求层多返回了一个服务状态数据。我通过DomainServiceDataReformerProtocol协议来固化两者的交互中介逻辑:

#import 

typedef NS_ENUM(NSUInteger, DomainServiceStatusType) {
    DomainServiceStatusSuccess = 1,
    DomainServiceStatusNoData,
    DomainServiceStatusNetworkFailure,
    DomainServiceStatusOnGoing,
    DomainServiceStatusPaused,
    DomainServiceStatusCanceled,
    DomainServiceStatusNoExecute
};

@protocol ListServiceReformer 

// 状态
@property (nonatomic, assign) DomainServiceStatusType status;
// 参数
@property (nonatomic, assign) NSInteger pageIndex;
// 每页数据量
@property (nonatomic, assign) NSInteger pageSize;
// 结果
@property (nonatomic, strong) NSArray *modelList;
// 错误信息
@property (nonatomic, copy) NSString *errorMsg;

@end

要实现真正的交互,那就得依赖于这个协议的实现类了,最简单的实现类可以做成一个基类ListServiceReformerImpl

///    ListServiceReformerImpl.h

#import "ListServiceReformer.h"
#import 

@interface ListServiceReformerImpl : NSObject 

/// 协议必须实现的部分///
// 状态
@property (nonatomic, assign) DomainServiceStatusType status;
// 参数
@property (nonatomic, assign) NSInteger pageIndex;
// 每页数据量
@property (nonatomic, assign) NSInteger pageSize;
// 结果
@property (nonatomic, strong) NSArray *modelList;
// 错误信息
@property (nonatomic, copy) NSString *errorMsg;

@end

///    ListServiceReformerImpl.m

#import "ListServiceReformerImpl.h"

@implementation ListServiceReformerImpl

@end

假如API要传的参数很简单,就只要传请求的页数序号,那么直接用这个基类实现类就能完成交互。而如果API还要求传其他参数,那么就得子类化这个基类,然后拓展参数,像HouseSourceListServiceReformerImpl

///    HouseSourceListServiceReformerImpl.h

#import "HouseSourceListServiceReformerImpl.h"

@interface HouseSourceListServiceReformerImpl :ListServiceReformerImpl

@property (nonatomic, assign) NSInteger projectId;
@property (nonatomic, assign) BOOL ownerType;
@property (nonatomic, assign) BOOL forSale;

@end

///    HouseSourceListServiceReformerImpl.m

#import "HouseSourceListServiceReformerImpl.h"

@implementation HouseSourceListServiceReformerImpl

@end

三,固化数据请求层和交互中介的交互逻辑
数据请求层是ListDomainService协议的实现类,它是怎么从交互中介那里拿到请求参数又是怎么把请求结果数据返回给它的呢?那就全看ListServiceReformer和ListDomainService这两个协议的交互了。ListDomainService的实现类用ListServiceReformer的实现类来初始化自己,这样它就可以拿到请求参数,然后请求得到结果后,在把结果解析封装后,放到ListServiceReformer实现类的modelList属性里面,或者如果请求出错就把错误信息放到它的errorMsg属性里面,还有吧请求当前状态,放到它的DomainServiceStatusType属性里。ListDomainService协议如下:

#import "ListServiceReformer.h"
#import 

@protocol ListDomainService 

// 返回初始化service的那个reformer
- (id)reformer;
// 设置参数
- (id)initWithReformer:(id)reformer;
// 执行请求
- (void)doService;
// 返回结果
- (void)serviceCompletion:(void (^)(id))completion;

@end

四,固化上层和数据请求层的交互逻辑
这个设计是做项目的时候一面做,一面想出来的,做的匆忙,有些不完善的地方,比如这里的交互中介其实没有把上层和数据请求层隔离开,而只是作为一个数据的传递者和缓存者,上层和数据请求层是直接交互的。TableViewManager基类通过属性引入了DomainService; 当需要执行数据请求是直接调用它的doService方法;在设置DomainService实现类对象时,通过在它的serviceCompletion:方法设置block来监听不同请求状态并设置不同响应。

///    TableViewManager.h, 删去其他代码只保留最必要的部分

@interface TableViewManager : NSObject 

@property (nonatomic, weak) id domainService;

@end

///    TableViewManager.m, 删去其他代码只保留最必要的部分

@implementation TableViewManager

- (void)setupDomainService{
    if (!self.domainService) {
        return;
    }
    [self.domainService serviceCompletion:^(id reformer) {
        switch (reformer.status) {
            case DomainServiceStatusSuccess:{
               ///    请求成功拿到数据后的操作
            }
                break;
            case DomainServiceStatusNoData:{
                ///    请求成功数据为空的操作
            }
                break;
            case DomainServiceStatusNetworkFailure:{
               ///    请求失败后的操作
            }
                break;
            default:
                break;
        }
    }];
}

- (void)loadFirstPage{
    if (self.domainService && [self.domainService reformer]) {
        [self.domainService reformer].pageIndex = FirstPageIndex;
        [self.domainService doService];
    }
}

- (void)loadNextPage{
    if (self.domainService && [self.domainService reformer]) {
        [self.domainService reformer].pageIndex ++;
        [self.domainService doService];
    }
}

@end

=============================实现类之间如何工作============================
只要是要展示列表数据的模块就适合利用这个迷你框架。下面举例房源详情的资讯列表。首先模块要创建一系列相关的子类或实现类,实现类的命名要能暗示它们的作用:

MVVM我的实践(二)_第1张图片
实现类.png

由Controller强引用和初始化TableViewManager或其子类,DomainService的实现类和ListServiceReformer的实现类。

///    ForSaleHouseListViewController.m, 有删改其他代码只保留最必要的部分

#import "ForSaleHouseListViewController.h"
#import "ForSaleHouseViewManager.h"
#import "HouseSourceListDomainServiceImpl.h"
#import "HouseSourceListServiceReformerImpl.h"

@interface ForSaleHouseListViewController ()

@property (nonatomic, strong) HouseSourceListServiceReformerImpl *reformer;
@property (nonatomic, strong) HouseSourceListServiceDomainServiceImpl *domainService;
@property (nonatomic, strong) HouseSourceViewManager *viewManager;

@end

@implementation ForSaleHouseListViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.reformer = [[HouseSourceListServiceReformerImpl alloc] init];
    self.reformer.projectId = self.projectId;
    self.reformer.forSale = YES;
    self.reformer.ownerType = YES;
    self.domainService = [[HouseSourceListDomainServiceImpl alloc] initWithReformer:self.reformer];
    self.viewManager = [[ForSaleHouseViewManager alloc] init];
    self.viewManager.domainService = self.domainService;
    [self.viewManager showTableInView:self.view];
    [self.viewManager loadFirstPage];
}

ForSaleHouseViewManager里面需要重写几个代理方法,有必要时也可以在里面定制表格的其他特性。这个类的代码主要定制适合当前模块的表格的Cell和行点击事件。

#import "ForSaleHouseViewManager.h"
#import "HouseSourceModel.h"
#import "ForSaleHouseListCell.h"

static NSString *reuseIdentifier = @"ForSaleHouseListCell";

@implementation ForSaleHouseViewManager

- (id)init{
    if (self = [super init]) {
        ///  这里可以对表格做一些别的定制
        [self.theTableView registerClass:[ForSaleHouseListCell class] forCellReuseIdentifier:reuseIdentifier];
    }
    return self;
}

#pragma mark - UITableView Delegate & DataSource

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
   ///  重写,使用自己的Cell高度计算方法
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    ///  重写,使用自己的Cell
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
   ///  重写,实现自己的Cell点击处理方法
}

那么处理数据的获取、加载、刷新等这些通用不变的逻辑还是交给TableViewManager这个基类来完成。TableViewManager自己可以做的事情有执行数据请求、定制是否实现上下拉刷新动画、显示表格等。同时,对于有可能需要被子类定制的tableView和数据源,它暴露了相关属性供子类使用。

#import "ListDomainService.h"
#import 
#import "TableViewManagerProtocol.h"

@interface TableViewManager : NSObject 

@property (nonatomic, weak) id domainService;
@property (nonatomic, strong) UITableView *theTableView;
@property (nonatomic, strong) NSMutableArray *listDatas;
@property (nonatomic, assign) BOOL autoDownRefreshUI; //是否让viewManager管理下拉刷新动画效果
@property (nonatomic, assign) BOOL autoUpRefreshUI; //是否让viewManager管理上拉刷新动画效果
@property (nonatomic, weak) UINavigationController *navVC;

- (void)showTableInView:(UIView *)view;
// 属性autoDownRefreshUI设为YES后,用这个方法来实现进入下拉刷新效果;
// 如果autoDownRefreshUI为NO,调用这个方法效果跟调用loadFirstPage一样
- (void)enterRefresh;
// 无刷新UI效果的数据请求方法
- (void)loadFirstPage;
- (void)loadNextPage;

TableViewManager基类主要处理的逻辑就是正确的数据分页刷新逻辑,这些逻辑的实现依赖的是ListDomainService和ListServiceReformer的协议,当没有这些协议的实现类参与的时候只是不会有效果而不会崩溃;当这些协议的实现类参与进来时,就会完成实际的功能;当这些协议的实现类不同时,就能获取到不同的数据,实现不同功能模块的列表数据展示。这样就实现了这些固定的通用的逻辑的一次写完多次使用的目的。下面是截取了部分用到这些协议的代码:

@implementation TableViewManager

#pragma mark - Initialization
///    根据请求结果状态考虑执行何种处理结果方法,请求状态由ListDomainService协议通过ListServiceReformer协议给出。
- (void)setupDomainService{
    if (!self.domainService) {
        return;
    }
    [self.domainService serviceCompletion:^(id reformer) {
        switch (reformer.status) {
            case DomainServiceStatusSuccess:{
                if (reformer.pageIndex <= FirstPageIndex) {
                    [self loadFirstPageSuccess];
                }else{
                    [self loadNextPageSuccess];
                }
            }
                break;
            case DomainServiceStatusNoData:{
                if (reformer.pageIndex <= FirstPageIndex) {
                    [self loadFirstPageNoData];
                }else{
                    [self loadNextPageNoData];
                }
            }
                break;
            case DomainServiceStatusNetworkFailure:{
                if (reformer.pageIndex <= FirstPageIndex) {
                    [self loadFirstPageNetworkFailure];
                }else{
                    [self loadFirstPageNetworkFailure];
                }
            }
                break;
            default:
                break;
        }
        if (self.autoDownRefreshUI || self.autoUpRefreshUI) {
            [self endRefreshUI];
        }
        [self showFailedTipsWithStatus:reformer.status];
        //做完一切UI和数据操作后,才执行子类自定义的操作
        if (self.loadCompletion) {
            self.loadCompletion([self.domainService reformer].modelList,[self.domainService reformer].errorMsg);
        }
        //做完一切针对本次请求结果的操作之后再重置reformer需要重置的值
        [self resetReformer];
    }];
}

#pragma mark - Public
///  这些请求数据方法的参数由ListServiceReformer协议拿到,实际执行请求是通过执行ListDomainService协议的方法来实现。
- (void)enterRefresh{
    [self beginRefreshUI];
}

- (void)loadFirstPage{
    if (self.domainService && [self.domainService reformer]) {
        [self.domainService reformer].pageIndex = FirstPageIndex;
        [self.domainService doService];
    }
}

- (void)loadNextPage{
    if (self.domainService && [self.domainService reformer]) {
        [self.domainService reformer].pageIndex ++;
        [self.domainService doService];
    }
}

#pragma mark - Properties

- (void)setDomainService:(id)domainService{
    _domainService = domainService;
    [self setupDomainService];
}

- (void)setAutoDownRefreshUI:(BOOL)autoDownRefreshUI{
    _autoDownRefreshUI = autoDownRefreshUI;
    @weakify(self);
    if (_autoDownRefreshUI) {
        [self.theTableView setYt_RefreshHeaderBlock:^{
            @strongify(self);
            [self loadFirstPage];
        }];
    }else{
        [self.theTableView setYt_RefreshHeaderBlock:nil];
    }
}

- (void)setAutoUpRefreshUI:(BOOL)autoUpRefreshUI{
    _autoUpRefreshUI = autoUpRefreshUI;
    @weakify(self);
    if (_autoUpRefreshUI) {
        [self.theTableView setYt_RefreshFooterBlock:^{
            @strongify(self);
            [self loadNextPage];
        }];
    }else{
        [self.theTableView setYt_RefreshFooterBlock:nil];
    }
}

#pragma mark - Domain Service Done
///  这些数据封装的方法也是依赖于由ListDomainService协议获取的ListServiceReformer协议提供的数据
- (void)loadFirstPageSuccess{
    [self.listDatas removeAllObjects];
    [self.listDatas addObjectsFromArray:[self.domainService reformer].modelList];
    if (self.beforeLoadFirstPage) {
        self.beforeLoadFirstPage (self.listDatas);
    }
    [self.theTableView reloadData];
}

- (void)loadFirstPageNoData{
    [self.listDatas removeAllObjects];
    if (self.beforeLoadFirstPage) {
        self.beforeLoadFirstPage (self.listDatas);
    }
    [self.theTableView reloadData];
}

- (void)loadFirstPageNetworkFailure{
    [[PopDialogAppService sharedInstance] showError:[self.domainService reformer].errorMsg];
}

- (void)loadNextPageSuccess{
    [self.listDatas addObjectsFromArray:[self.domainService reformer].modelList];
    if (self.beforeLoadNextPage) {
        self.beforeLoadNextPage (self.listDatas);
    }
    [self.theTableView reloadData];
}

- (void)loadNextPageNoData{
    if ([self.domainService reformer].pageIndex > FirstPageIndex) {
        [self.domainService reformer].pageIndex --;
    }
}

- (void)loadNextPageNetworkFailure{
    if ([self.domainService reformer].pageIndex > FirstPageIndex) {
        [self.domainService reformer].pageIndex --;
    }
    [[PopDialogAppService sharedInstance] showError:[self.domainService reformer].errorMsg];
}

@end

HouseSourceListServiceReformerImpl类(代码之前有提到)针对具体的API要求做了参数拓展,如果API只要求传页码序号,那这个类可以不用实现,直接用基类ListServiceReformerImpl即可。

HouseSourceListDomainServiceImpl是必须实现的类,负责模块的数据获取,也是框架中数据请求层要实现要替换的类。请求逻辑比较特别时,这个类可以完全重写,比如这个例子的数据请求,它不同于一般列表只从一个API获取数据,它要从两个API根据情况获取和拼接数据,获取到数据后的结果处理逻辑很不同,所以要完全重新写一个实现类,而不是继承一个ListDomainServiceImpl的基类(下面会讲到)利用里面通用的结果处理逻辑。不过,哪怕数据请求层的逻辑发生多么天翻复地的变化,只要这些数据仍然要以列表的方式用表格视图展示,那么这个框架就还是适用于它,那么它的变动还是只停留在数据请求层的这个实现类,不会影响到上层代码,我们上面构建的协议的交互还是成立的。由于没有基类继承,HouseSourceListDomainServiceImpl要实现所有从数据请求到结果封装传递的逻辑,代码量会比较大。

#import "HouseSourceListDomainServiceImpl.h"
#import "HouseSourceListServiceReformerImpl.h"
#import "HouseSourceModel.h"


@interface HouseSourceListDomainServiceImpl()

@property (nonatomic, strong) HouseSourceListServiceReformerImpl *reformer;
@property (nonatomic, copy) void (^completion)(id);

@end

@implementation HouseSourceListDomainServiceImpl

#pragma mark - Reformer

/// 下面代表了一大段的ListDomainService协议实现代码

///  要实现初始化方法
- (id)initWithReformer:(id)reformer{
    if (self = [super init]) {
        self.reformer = reformer;
        //一定要依赖reformer,否则就返回nil
        if (!self.reformer) {
            return nil;
        }
    }
    return self;
}

- (void)doService{
    ///  要实现doService代理方法
}

- (void)serviceCompletion:(void (^)(id))completion{
    ///  要实现serviceCompletion:代理方法
}

- (id)reformer{
    ///  要实现reformer代理方法
}

#pragma mark - API

///  下面代表了一大段的API的调用方法的实现代码
- (void)requestForSaleOwnerList{

}

- (void)requestForRentOwnerList{
    
}

- (void)requestForSaleQuickMatchList{
   
}

- (void)requestForRentQuickMatchList{
   
}

#pragma mark - Results Process

/// 下面代表了一大段的结果处理逻辑代码
- (void)doneWithFirstApiResponse:(id)response{
    
}

- (void)doneWithSecondApiResponse:(id)response{
   
}

- (NSArray *)modelListWithResponse:(id)response{
    
}

- (void)doneWithModelList:(NSArray *)list{
  
}

- (void)doneWithError:(NSString *)error{
    
}

- (void)doCompletion{
   
}

#pragma mark - Model Transform

/// 下面代表了一大段的将数据由字典转换成model的代码

- (HouseSourceModel *)fillModelWithDic:(NSDictionary *)dic {
    
}

@end

而如果这个列表是普通的列表,它只处理单个API获取数据的情况,那么这个ListDomainService的实现类就会简单很多了,因为我做了ListDomainServiceImpl这个基类的封装,在里面实现了大部分对请求结果的通用处理逻辑,还增强了其他功能,比如添加了创建测试数据功能,方便在接口没完成时就能测试列表功能。只要继承了基类,那么实现类的子类就基本只处理一下Model转换的代码就行了。比如下面我是业主列表的数据请求层实现类,它是继承了ListDomainServiceImpl的子类,它只需要设置它自己的model转换逻辑modelTransform,设置它自己的列表数据在response里面的key路径,调用自己的API请求方法,然后在请求方法的success的block里面调用基类的[self doneWithResponse:response]方法,在failure的block里面调用基类的[self doneWithError:errorMsg]方法,它就能完成作为数据请求层实现着的职责。

#import "OwnerHouseSourceManageListDomainServiceImpl.h"
#import "HouseSourceManageModel.h"

@interface OwnerHouseSourceManageListDomainServiceImpl()

@end

@implementation OwnerHouseSourceManageListDomainServiceImpl

- (instancetype)initWithReformer:(id)reformer{
    if (self = [super initWithReformer:reformer]) {
        [self initialization];
    }
    return self;
}

- (void)initialization{
    @weakify(self);
    ///  指定自己的列表数据在response里面的key路径
    self.listKeys = @[@"Data",@"HouseList"];
    
    [self modelTransform:^id(NSDictionary *dic) {
        if (!dic) {
            return nil;
        }
        ///  一段将字典转换为model的代码,如果没必要转,可以不设置这个block
        return model;

    }];
    
    [self customService:^{
        @strongify(self);
        ///  调用自己的API请求方法
        [self exeGetMyHouseList];
    }];
}

#pragma mark - API

- (void)exeGetMyHouseList{
        [api startWithSuccess:{
            ///  已由基类实现的API请求成功后结果处理方法
            [self doneWithResponse:response];
        }
        failure:{
             ///  已由基类实现的API请求失败后结果处理方法
            [self doneWithError:errorMsg];
        }];
}

@end

ListDomainServiceImpl基类的部分代码

#import "ListDomainServiceImpl.h"

static NSString *ResponseDataKey = @"Data";
static NSString *ResponseListKey = @"List";

static NSString *ResponsePageSizeKey = @"PageSize";

@interface ListDomainServiceImpl ()

@property (nonatomic, weak) id reformer;
@property (nonatomic, copy) void (^customService)();
@property (nonatomic, copy) void (^completion)(id);
@property (nonatomic, copy) id (^modelTransform)(NSDictionary *dic);

@end

@implementation ListDomainServiceImpl

#pragma mark - ListDomainService
// 设置参数
- (id)initWithReformer:(id)reformer {
    if (self = [super init]) {
        self.reformer = reformer;
        //一定要依赖reformer,否则就返回nil
        if (!self.reformer) {
            assert(@"必须有可用的Reformer对象");
            self.testWholeListCount = TestDefaultListCount;
            self.testPageSize = TestDefaultPageSize;
            self.testPageIndex = TestDefaultPageIndex;
            // return nil;
        }
    }
    return self;
}

//执行服务
- (void)doService {
    if (!self.reformer) {
        return;
    }
    if (self.customService) {
        self.customService();
    }
}

// 返回结果
- (void)serviceCompletion:(void (^)(id))completion {
    self.completion = completion;
}

- (id)reformer {
    return _reformer;
}

#pragma mark - Request

- (void)customService:(void (^)())block {
    self.customService = block;
}

#pragma mark - Results Handle

- (void)doneWithResponse:(id)response {

    NSInteger pageSize = [self pageSizeFromResponse:response];
    NSArray *dataList = [self dataListFromResponse:response];
    NSArray *modelList = [self modelListFromDataList:dataList transform:self.modelTransform];
    [self doneWithPageSize:pageSize];
    [self doneWithModelList:modelList];
}

- (void)doneWithPageSize:(NSInteger)pageSize{
    if (!self.reformer) {
        return;
    }
    self.reformer.pageSize = pageSize;
}

- (void)doneWithModelList:(NSArray *)list {
    if (!self.reformer) {
        return;
    }
    if ([list count] > 0) {
        self.reformer.status = DomainServiceStatusSuccess;
        self.reformer.modelList = list;
    } else {
        self.reformer.status = DomainServiceStatusNoData;
        self.reformer.modelList = nil;
    }
    [self doCompletion];
}

- (void)doneWithError:(NSString *)error {
    if (!self.reformer) {
        return;
    }
    self.reformer.status = DomainServiceStatusNetworkFailure;
    self.reformer.errorMsg = error;
    [self doCompletion];
}

- (void)doCompletion {
    if (self.completion && self.reformer) {
        self.completion(self.reformer);
    }
}

#pragma mark - Data Encapsulation

- (NSArray *)dataListFromResponse:(id)response {
    if (!response) {
        return nil;
    }
    if ([self.listKeys count] == 2) {
        id obj = [self valueForKey:self.listKeys[0] inObj:response];
        if (!obj) {
            return nil;
        }
        id obj1 = [self valueForKey:self.listKeys[1] inObj:obj];
        if (!obj1) {
            return nil;
        }
        if (![obj1 isKindOfClass:[NSArray class]]) {
            return nil;
        }
        return obj1;
    } else if ([self.listKeys count] == 1) {
        id obj = [self valueForKey:self.listKeys[0] inObj:response];
        if (!obj) {
            return nil;
        }
        if (![obj isKindOfClass:[NSArray class]]) {
            return nil;
        }
        return obj;
    } else {
        id obj = [self valueForKey:ResponseDataKey inObj:response];
        if (!obj) {
            return nil;
        }

        id obj1 = [self valueForKey:ResponseListKey inObj:obj];

        if (!obj1) {
            return nil;
        }
        if (![obj1 isKindOfClass:[NSArray class]]) {
            return nil;
        }
        return obj1;
    }
}
- (void)modelTransform:(id (^)(NSDictionary *))block {
    self.modelTransform = block;
}

- (NSArray *)modelListFromDataList:(NSArray *)dataList transform:(id (^)(NSDictionary *dic))block {
    if (!dataList || [dataList count] == 0) {
        return nil;
    }
    if (!block) {
        return dataList;
    }
    NSMutableArray *modelList = [NSMutableArray array];
    for (NSDictionary *dic in dataList) {
        id model = block(dic);
        if (model) {
            [modelList addObject:model];
        }
    }
    return modelList;
}

- (NSInteger)pageSizeFromResponse:(id)response{
    if (!response) {
        return -1;
    }
    if ([self.pageSizeKeys count] == 2) {
        id obj = [self valueForKey:self.pageSizeKeys[0] inObj:response];
        if (!obj) {
            return -1;
        }
        id obj1 = [self valueForKey:self.pageSizeKeys[1] inObj:obj];
        if (!obj1) {
            return -1;
        }
        if (![obj1 isKindOfClass:[NSNumber class]]) {
            return -1;
        }
        return [obj1 integerValue];
    } else if ([self.pageSizeKeys count] == 1) {
        id obj = [self valueForKey:self.pageSizeKeys[0] inObj:response];
        if (!obj) {
            return -1;
        }
        if (![obj isKindOfClass:[NSNumber class]]) {
            return -1;
        }
        return [obj integerValue];
    } else {
        id obj = [self valueForKey:ResponseDataKey inObj:response];
        if (!obj) {
            return -1;
        }
        id obj1 = [self valueForKey:ResponsePageSizeKey inObj:obj];
        if (!obj1) {
            return -1;
        }
        if (![obj1 isKindOfClass:[NSNumber class]]) {
            return -1;
        }
        return [obj1 integerValue];
    }

}

@end

你可能感兴趣的:(MVVM我的实践(二))