MVVM我的實踐(二)

【ViewModel、AppService和DomainService怎么分工協(xié)作】

前面說到ViewModel把很多事情代理給了AppService去做,其實還有另外一部分事情它代理給DomainService去做了,這里說的AppService和DomainService不一定是一個具體的類,它們可以是幾個這種類型的類,或者它們可以是這種類型的類的協(xié)議,而不是具體實現(xiàn)類。這里我沒有說應(yīng)該怎么怎么做,因為我在寫ViewModel的過程中也存在這些類的各種組合情況,沒有固定的模式。不過,在我慢慢探索的過程中,我還是發(fā)現(xiàn)了可能遵循以下原則會比較好:

  1. 依賴協(xié)議而不是依賴實現(xiàn)。
  2. 要分層,每層只做自己該做的事。
  3. 盡量不要跨層交互。

分層的好處是明顯的,如果分層的職責(zé)分配的合理,分析代碼時會很清晰,哪一層的代碼問題去哪一層找;有利于隔離變化,保持代碼最大程度的重用,當(dāng)移植時,變化到哪一層就替換哪一層,其他不變的層便是可以重用的。
依賴協(xié)議而不依賴實現(xiàn)便是一種分層所仰賴的重要技術(shù),有了它分層才能實現(xiàn)替換變化的代碼,保持不變的代碼被重用。
每層只做自己該做的事,盡量不要跨層交互,就是分層的重要基礎(chǔ),沒有按照這個原則去分層,那么也不可能實現(xiàn)恰當(dāng)?shù)母綦x變化層次用于代替,保持其他部分層次的穩(wěn)定重用。

下面用“MVVM我的實踐(一)”里面提到的小群組主頁ViewModel示例來講解Controller/View,ViewModel,AppService和DomainService這些類之間怎么形成分層,去分工協(xié)作做好自己分內(nèi)的事情。代碼只展示示意性的代碼,有刪除其他代碼,同時有的代碼我也修改了,不是項目原來的代碼,因為原來的代碼有很多寫得不好,無法表達出我要表達的東西。

Controller/View代表的顯示層的職責(zé):
作為無腦的顯示層,它們只需要拿到數(shù)據(jù),不做判斷,只做直接呈現(xiàn)。就行下面的分頁菜單self.slideMenu,它只是針對ViewModel在用到它刷新時,去執(zhí)行對每個菜單項,繪制不同類型的badge圖標,至于為什么是這個菜單項,為什么是這種類型badge,它不關(guān)心。

- (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代表的大腦層(控制層)(中樞層)的職責(zé):

  1. 接受表現(xiàn)層(Controller/View)提供的信息,改變自己的狀態(tài),反饋給表現(xiàn)層。
  2. 接受表現(xiàn)層傳遞的交互,執(zhí)行對交互的響應(yīng)命令,改變自己的狀態(tài),反饋給表現(xiàn)層。

直接通過屬性從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內(nèi)的交互事件:

    // 動態(tài)
    GroupDynamicListViewController *dynamicVC = [[GroupDynamicListViewController alloc] init];
    dynamicVC.title = @"動態(tài)";
    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數(shù)據(jù)和緩存數(shù)據(jù)的對比分析,決定要不要顯示badge,對哪個菜單,顯示什么badge。但是它并不直接獲取API數(shù)據(jù)和緩存數(shù)據(jù),它只是在需要它們時通過外界傳入自身的self.appService來取得他要的東西。那么為什么不讓它管這兩個任務(wù)而要代理給別的類完成,我的考慮有幾點:一,獲取API數(shù)據(jù)、獲取緩存數(shù)據(jù),這兩種功能很容易讓人想到要封裝成單獨的組件,這樣在別的地方也能夠重用它們了;二,這兩種功能實現(xiàn)起來估計要依賴不少其他類,而我覺得ViewModel不應(yīng)該依賴那么多其他那些與它的邏輯任務(wù)不直接相關(guān)的類,而這里ViewModel的任務(wù)只是利用這些數(shù)據(jù)做邏輯判斷,而不管數(shù)據(jù)的獲取過程;三,尤為重要的是,當(dāng)要在別的地方重用這個ViewModel時,這兩個獲取數(shù)據(jù)的方式很有可能就不一樣了,我希望到時候能夠輕易替換它們的實現(xiàn),而又不需要更改或者大改ViewModel原有的代碼。

ViewModel的狀態(tài)確定了之后,得讓表現(xiàn)層對其狀態(tài)做出反饋,這里剩余的跟UI有關(guān)的事情,它就通過block代理出去給Controller去做了,在這個代理的過程中,它也已經(jīng)幫Controller把任務(wù)精細化了,這里它并沒有通過showDotForIndex(NSInteger index)這種帶參的block將事件丟給Controller,因為那樣的話Controller還得對index做判斷,以確定到底最終要給哪個菜單顯示badge,而是直接通過
-(void)hasNewDynamic:(update)update;這類的命名很明確的方法,讓Controller知道要從這里設(shè)置回調(diào)block來針對Dynamic的菜單要做badge更新。這里的命名比較講究,方法不要這樣命名-(void)showDotBadgeForSecondIndex:(update)update;因為以后可能Dynamic菜單不是放在second Index位置了,顯示badge的方式也不是dot的方式了,那這個方法名字就會給人誤導(dǎo)、迷惑。所以為了保證ViewModel在處理這種變化方便的通用性,方法的命名不要與其最終的實現(xiàn)有太大的關(guān)聯(lián)。那么,說到通用性,這種方法-(void)hasNewDynamic:(update)update;也給人一種很專用的感覺啊,畢竟以后這個位置的菜單不是Dynamic菜單,而是別的菜單了呢?只能說,在這種業(yè)務(wù)層次來講,這里的這個ViewModel就是一個專用的ViewModel了,它只適合有Dynamic菜單的這個業(yè)務(wù)模塊使用,不適合換成別的菜單的別的業(yè)務(wù)模塊,所以像這樣專門性的、意義明確的方法是適合它的。很多時候,我發(fā)覺要把ViewModel的通用性設(shè)計好很不容易,你不能把它做得太通用了,否則用到它的地方得做很多專用性的判斷,就比如暴露showDotForIndex(NSInteger index)這樣的block,那么每次用到它時,使用方都得做index的判斷,這樣的話我認為ViewModel并沒有實現(xiàn)易用性這個目標,因為它針對性不強。你也不能把它做得太專用了,就比如暴露-(void)showDotBadgeForSecondIndex:(update)update;這樣的接口,它的針對性就太強了,連怎么實現(xiàn)UI效果都給你指定了,這就超出它的職責(zé)了。所以,針對這個例子里面的ViewModel我的設(shè)計原則是,一,要讓它有針對性地服務(wù)于它所屬的模塊,暴露的接口要能夠見名知意,而且盡量不讓使用方要對type、index之類的參數(shù)做判斷;二,不該它管的事情它不管,接口命名要彰顯業(yè)務(wù)意義大于操作意義。


- (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();
    }
}

最后形成自身狀態(tài)的改變,反饋給表現(xiàn)層去刷新UI。通過精細、單一任務(wù)的接口設(shè)計,Controller很容易知道針對什么狀態(tài),去什么方法去設(shè)置響應(yīng)的回調(diào)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代表的執(zhí)行層(干雜活的)的職責(zé)
AppService對于ViewModel是一個服務(wù)層,這個層可以由一個或多個AppService的相關(guān)類、對象組成,只要ViewModel有什么事情不能直接做的,就交給它們做。只要一個ViewModel對Controller揚言要接管一整個模塊功能或一整個流程的運作,那么它要處理的事情就是多而雜的。就這個例子而言,ViewModel要做完整個模塊的事情,起碼要涉及好幾項它不應(yīng)該直接去參與的事情,一,API數(shù)據(jù)請求;二,數(shù)據(jù)緩存的讀寫;三,請求成功與失敗的彈窗提示。
在ViewModel該如何去做那些不應(yīng)該直接由它做的事情的問題上有幾個可以實現(xiàn)的方案。
一,ViewModel作為功能實現(xiàn)類直接去做那些事??隙ú唤ㄗh這樣。那不是它該做的。
二,ViewModel讓其他功能實現(xiàn)類去做那些事。也不建議這樣。比如如果實現(xiàn)類是一個View的類,那樣ViewModel就直接關(guān)聯(lián)View了。
三,ViewModel用一些AppService實現(xiàn)類(通常繼承自NSObject)去操控那些功能實現(xiàn)類完成那些事情,ViewModel只若引用這些AppService實現(xiàn)類。這種方案可以避免ViewModel有可能直接強引用到View。但是這樣一來,ViewModel還是會有依賴多個類。
四,ViewModel只若引用一個AppService實現(xiàn)類,所有不屬于它做的事情,都代理給這個AppService類去做。這種方案可以避免ViewModel依賴多個類。但是當(dāng)要移植ViewModel時,這個AppService實現(xiàn)類可能不適用了,要更換,這樣的話會導(dǎo)致ViewModel代碼的修改。
五,ViewModel只若引用一個AppService的協(xié)議,當(dāng)ViewModel要移植它處使用時,AppService協(xié)議跟著移植,只需要替換AppService協(xié)議的實現(xiàn)類。這種方案可以讓ViewModel能夠不受AppService實現(xiàn)類更換的影響,達到隔離變化,穩(wěn)定可重用代碼的效果。
以上三到五的方案我都用過,應(yīng)該說很多情況來看五是最好的。不過五的方案會導(dǎo)致這個唯一的AppService變得要做的事情很龐雜,什么都管,感覺職責(zé)很模糊。這種時候我覺得可以通過適當(dāng)?shù)睦^承和組合技術(shù),來給它瘦身,讓它變得功能更聚焦、職責(zé)更清晰。比如很多列表類ViewModel的AppService實現(xiàn)類都需要處理請求錯誤提示、無數(shù)據(jù)提示這樣的功能,那么就創(chuàng)建一個ListBaseAppServiceImpl這樣AppService實現(xiàn)類的基類,它里面包含了對這兩種功能的服務(wù)的實現(xiàn),這個基類的子類就可以只關(guān)注它們所對應(yīng)功能模塊的特別的服務(wù)的實現(xiàn)即可。又比如,有的服務(wù)類需要用到緩存服務(wù),而我們又不想把緩存服務(wù)的接口由這個服務(wù)實現(xiàn)類來暴露的話,就可以專門創(chuàng)建一個CacheAppService的緩存協(xié)議,把緩存協(xié)議實現(xiàn)類這作為它的一個屬性,或者讓它通過一個方法返回緩存協(xié)議的實現(xiàn)類,再在這個協(xié)議里面暴露緩存服務(wù)的接口。
回到小群組主頁ViewModel這個例子,AppService層提供了ViewModel所需的所有數(shù)據(jù),以及這些數(shù)據(jù)的請求和緩存操作支持,它讓ViewModel像個戰(zhàn)場上的指揮官一樣,不必親自去獲取情報、不必親自去上戰(zhàn)場搏斗,就能夠輕松地指揮作戰(zhàn),謀劃戰(zhàn)局;它又像一個管家,而ViewModel像個房子主人一樣,主人只需要關(guān)注如何裝點房子,至于需要用到什么材料、飾品,只需要吩咐管家一聲就能得到,要擺放什么物體,修改什么墻面,只需要吩咐管家一聲,管家就會幫做好:

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

@protocol GroupMainAppService <NSObject>

- (instancetype)initWithDomainService:(id<GroupMainDomainService>)service
                      cacheService:(id<CacheAppService>)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) id<GroupMainDomainService>domainService;
@property (nonatomic, weak) id<CacheAppService>cacheService;
@property (nonatomic, assign) NSInteger userId;

@end

@implementation GroupMainAppServiceImpl

- (instancetype)initWithDomainService:(id<GroupMainDomainService>)service cacheService:(id<CacheAppService>)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服務(wù)層的職責(zé)
如果ViewModel繞過AppService而直接使用DomainService,那么DomainService對ViewModel而言就是一個API服務(wù)層,專職為ViewModel獲取服務(wù)端數(shù)據(jù)。如果ViewModel只與AppService打交道,那么DomainService就是AppService的API服務(wù)層,專職為AppService獲取服務(wù)端數(shù)據(jù)。專門設(shè)置DomainService作為一層意義是明顯的,畢竟移動APP主要依賴的就是API,不同的APP依賴不同的API,而不同的APP有可能有相同可共用的ViewModel,因為有不少APP邏輯是通用的,這時候,為了讓ViewModel可重用,API可替換,就要讓用到API的地方(這里是ViewModel或者AppService)依賴一個DomainService層,而且是依賴協(xié)議而非實現(xiàn)類,這樣只要協(xié)議不變,基于協(xié)議的上層代碼就可以不變,只需要更換作為實現(xiàn)的DomainServiceImpl類即可。

下面是DomainService協(xié)議:

#import <Foundation/Foundation.h>

@protocol GroupMainDomainService <NSObject>

- (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

小結(jié):
如果用一個比喻來闡述Controller、View、ViewModel、AppService、DomainService的關(guān)系,我覺得Controller就像一間商店,View是商店里面的商品,ViewModel是店主,AppService是打雜的,DomainService是拉貨的。商店存放著商品,駐守著店長、打雜員工、拉貨司機。顧客進入一家店后,店主便承擔(dān)起了接待的任務(wù),為了服務(wù)好顧客,商品怎么擺、怎么用由他說了算,打雜員工做什么事情也由他安排,拉貨司機要去補什么貨也由他來指揮。顧客在這家店逛完了,他可以讓打雜的帶顧客去逛另由另一店主管的另一家店,如果顧客要去的下一家店還由他來管,他就直接和顧客一起由打雜員工送他們光臨下一家店。總之,在所有流程里面,店主都起著決策和指揮的左右,其他人員服從管理去執(zhí)行具體任務(wù),商店是被支配的場所,它有時候是人員的歸屬地有時候只是人員的落腳點,商品是被使用的物件。這些角色所做的所有事情都為了接待好顧客,讓他們有一個良好的觀光、游玩或購物體驗。

【繼續(xù)說說依賴協(xié)議編程】

我在項目里這方面比較典型的一個應(yīng)用是用在處理列表性API的數(shù)據(jù)請求到數(shù)據(jù)展示這一套流程上面,因為我發(fā)現(xiàn),每次我寫列表API的請求邏輯的時候,按照以往的做法,總需要在API的blocks里面重新寫一套結(jié)果數(shù)據(jù)封裝、界面刷新、請求結(jié)果提示、無數(shù)據(jù)提示等各種邏輯。本來就是有點復(fù)雜,又重復(fù)的東西,還每次都得根據(jù)不同接口寫一遍,不僅覺得沒必要,還容易出錯。那么好的做法當(dāng)然就是想辦法讓通用的代碼只需費心寫好一次,后面在其他地方輕松調(diào)用,再配合需要變動的部分,就能實現(xiàn)一個新API,新模塊的完整功能。
要實現(xiàn)這個目標,首先得提取出那些不變的邏輯代碼,用基類和協(xié)議把它們固話下來,而變化的部分就要能夠通過子類和實現(xiàn)類來實現(xiàn)定制和替換。在這個設(shè)計里面,不變的部分是數(shù)據(jù)表格的分頁刷新邏輯、數(shù)據(jù)接口的分頁加載邏輯、界面和API的數(shù)據(jù)傳遞邏輯、接口的數(shù)據(jù)解析和封裝邏輯、界面的結(jié)果狀態(tài)顯示邏輯等;變動的部分是界面結(jié)果狀態(tài)提示語、數(shù)據(jù)表格Cell的樣式、界面給到API的參數(shù)、API返回結(jié)果的字段、API數(shù)據(jù)結(jié)果的封裝等。
一,封裝表格的通用邏輯
項目里但凡用到表格的地方有很多邏輯都是一樣的,一樣的表格初始化方式、一樣要實現(xiàn)那幾個常用的代理,所以我把這兩部分的邏輯都放到了一個基類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];
}

當(dāng)用在不同的模塊時,只需要子類話這個基類,重寫幾個屬性就行,比如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];
}

二,固化上層與數(shù)據(jù)請求層的交互中介邏輯
這里的上層指TableViewManager基類,數(shù)據(jù)請求層指DomainService協(xié)議(下面會說到)的實現(xiàn)類。這里的交互中介固定邏輯是指上層需要往交互中介傳什么參數(shù)給數(shù)據(jù)請求層;數(shù)據(jù)請求層需要把什么結(jié)果通過交互中介返回給上層。上層需要傳遞的固定參數(shù)是請求的頁碼序號,數(shù)據(jù)請求層需要返回給上層的數(shù)據(jù)固定有三個,每頁數(shù)據(jù)量、數(shù)據(jù)列表(可以為空)、錯誤信息(請求錯誤時就會非空),為了讓上層能夠針對數(shù)據(jù)請求層的各種狀態(tài)做好相應(yīng)的響應(yīng),我還讓數(shù)據(jù)請求層多返回了一個服務(wù)狀態(tài)數(shù)據(jù)。我通過DomainServiceDataReformerProtocol協(xié)議來固化兩者的交互中介邏輯:

#import <Foundation/Foundation.h>

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

@protocol ListServiceReformer <NSObject>

// 狀態(tài)
@property (nonatomic, assign) DomainServiceStatusType status;
// 參數(shù)
@property (nonatomic, assign) NSInteger pageIndex;
// 每頁數(shù)據(jù)量
@property (nonatomic, assign) NSInteger pageSize;
// 結(jié)果
@property (nonatomic, strong) NSArray *modelList;
// 錯誤信息
@property (nonatomic, copy) NSString *errorMsg;

@end

要實現(xiàn)真正的交互,那就得依賴于這個協(xié)議的實現(xiàn)類了,最簡單的實現(xiàn)類可以做成一個基類ListServiceReformerImpl

///    ListServiceReformerImpl.h

#import "ListServiceReformer.h"
#import <Foundation/Foundation.h>

@interface ListServiceReformerImpl : NSObject <ListServiceReformer>

/// 協(xié)議必須實現(xiàn)的部分///
// 狀態(tài)
@property (nonatomic, assign) DomainServiceStatusType status;
// 參數(shù)
@property (nonatomic, assign) NSInteger pageIndex;
// 每頁數(shù)據(jù)量
@property (nonatomic, assign) NSInteger pageSize;
// 結(jié)果
@property (nonatomic, strong) NSArray *modelList;
// 錯誤信息
@property (nonatomic, copy) NSString *errorMsg;

@end

///    ListServiceReformerImpl.m

#import "ListServiceReformerImpl.h"

@implementation ListServiceReformerImpl

@end

假如API要傳的參數(shù)很簡單,就只要傳請求的頁數(shù)序號,那么直接用這個基類實現(xiàn)類就能完成交互。而如果API還要求傳其他參數(shù),那么就得子類化這個基類,然后拓展參數(shù),像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

三,固化數(shù)據(jù)請求層和交互中介的交互邏輯
數(shù)據(jù)請求層是ListDomainService協(xié)議的實現(xiàn)類,它是怎么從交互中介那里拿到請求參數(shù)又是怎么把請求結(jié)果數(shù)據(jù)返回給它的呢?那就全看ListServiceReformer和ListDomainService這兩個協(xié)議的交互了。ListDomainService的實現(xiàn)類用ListServiceReformer的實現(xiàn)類來初始化自己,這樣它就可以拿到請求參數(shù),然后請求得到結(jié)果后,在把結(jié)果解析封裝后,放到ListServiceReformer實現(xiàn)類的modelList屬性里面,或者如果請求出錯就把錯誤信息放到它的errorMsg屬性里面,還有吧請求當(dāng)前狀態(tài),放到它的DomainServiceStatusType屬性里。ListDomainService協(xié)議如下:

#import "ListServiceReformer.h"
#import <Foundation/Foundation.h>

@protocol ListDomainService <NSObject>

// 返回初始化service的那個reformer
- (id<ListServiceReformer>)reformer;
// 設(shè)置參數(shù)
- (id)initWithReformer:(id<ListServiceReformer>)reformer;
// 執(zhí)行請求
- (void)doService;
// 返回結(jié)果
- (void)serviceCompletion:(void (^)(id<ListServiceReformer>))completion;

@end

四,固化上層和數(shù)據(jù)請求層的交互邏輯
這個設(shè)計是做項目的時候一面做,一面想出來的,做的匆忙,有些不完善的地方,比如這里的交互中介其實沒有把上層和數(shù)據(jù)請求層隔離開,而只是作為一個數(shù)據(jù)的傳遞者和緩存者,上層和數(shù)據(jù)請求層是直接交互的。TableViewManager基類通過屬性引入了DomainService; 當(dāng)需要執(zhí)行數(shù)據(jù)請求是直接調(diào)用它的doService方法;在設(shè)置DomainService實現(xiàn)類對象時,通過在它的serviceCompletion:方法設(shè)置block來監(jiān)聽不同請求狀態(tài)并設(shè)置不同響應(yīng)。

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

@interface TableViewManager : NSObject 

@property (nonatomic, weak) id<ListDomainService> domainService;

@end

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

@implementation TableViewManager

- (void)setupDomainService{
    if (!self.domainService) {
        return;
    }
    [self.domainService serviceCompletion:^(id<ListServiceReformer> reformer) {
        switch (reformer.status) {
            case DomainServiceStatusSuccess:{
               ///    請求成功拿到數(shù)據(jù)后的操作
            }
                break;
            case DomainServiceStatusNoData:{
                ///    請求成功數(shù)據(jù)為空的操作
            }
                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

=============================實現(xiàn)類之間如何工作============================
只要是要展示列表數(shù)據(jù)的模塊就適合利用這個迷你框架。下面舉例房源詳情的資訊列表。首先模塊要創(chuàng)建一系列相關(guān)的子類或?qū)崿F(xiàn)類,實現(xiàn)類的命名要能暗示它們的作用:

實現(xiàn)類.png

由Controller強引用和初始化TableViewManager或其子類,DomainService的實現(xiàn)類和ListServiceReformer的實現(xiàn)類。

///    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里面需要重寫幾個代理方法,有必要時也可以在里面定制表格的其他特性。這個類的代碼主要定制適合當(dāng)前模塊的表格的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 {
   ///  重寫,實現(xiàn)自己的Cell點擊處理方法
}

那么處理數(shù)據(jù)的獲取、加載、刷新等這些通用不變的邏輯還是交給TableViewManager這個基類來完成。TableViewManager自己可以做的事情有執(zhí)行數(shù)據(jù)請求、定制是否實現(xiàn)上下拉刷新動畫、顯示表格等。同時,對于有可能需要被子類定制的tableView和數(shù)據(jù)源,它暴露了相關(guān)屬性供子類使用。

#import "ListDomainService.h"
#import <Foundation/Foundation.h>
#import "TableViewManagerProtocol.h"

@interface TableViewManager : NSObject <UITableViewDelegate, UITableViewDataSource,TableViewManagerProtocol>

@property (nonatomic, weak) id<ListDomainService> 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設(shè)為YES后,用這個方法來實現(xiàn)進入下拉刷新效果;
// 如果autoDownRefreshUI為NO,調(diào)用這個方法效果跟調(diào)用loadFirstPage一樣
- (void)enterRefresh;
// 無刷新UI效果的數(shù)據(jù)請求方法
- (void)loadFirstPage;
- (void)loadNextPage;

TableViewManager基類主要處理的邏輯就是正確的數(shù)據(jù)分頁刷新邏輯,這些邏輯的實現(xiàn)依賴的是ListDomainService和ListServiceReformer的協(xié)議,當(dāng)沒有這些協(xié)議的實現(xiàn)類參與的時候只是不會有效果而不會崩潰;當(dāng)這些協(xié)議的實現(xiàn)類參與進來時,就會完成實際的功能;當(dāng)這些協(xié)議的實現(xiàn)類不同時,就能獲取到不同的數(shù)據(jù),實現(xiàn)不同功能模塊的列表數(shù)據(jù)展示。這樣就實現(xiàn)了這些固定的通用的邏輯的一次寫完多次使用的目的。下面是截取了部分用到這些協(xié)議的代碼:

@implementation TableViewManager

#pragma mark - Initialization
///    根據(jù)請求結(jié)果狀態(tài)考慮執(zhí)行何種處理結(jié)果方法,請求狀態(tài)由ListDomainService協(xié)議通過ListServiceReformer協(xié)議給出。
- (void)setupDomainService{
    if (!self.domainService) {
        return;
    }
    [self.domainService serviceCompletion:^(id<ListServiceReformer> 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和數(shù)據(jù)操作后,才執(zhí)行子類自定義的操作
        if (self.loadCompletion) {
            self.loadCompletion([self.domainService reformer].modelList,[self.domainService reformer].errorMsg);
        }
        //做完一切針對本次請求結(jié)果的操作之后再重置reformer需要重置的值
        [self resetReformer];
    }];
}

#pragma mark - Public
///  這些請求數(shù)據(jù)方法的參數(shù)由ListServiceReformer協(xié)議拿到,實際執(zhí)行請求是通過執(zhí)行ListDomainService協(xié)議的方法來實現(xiàn)。
- (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<ListDomainService>)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
///  這些數(shù)據(jù)封裝的方法也是依賴于由ListDomainService協(xié)議獲取的ListServiceReformer協(xié)議提供的數(shù)據(jù)
- (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要求做了參數(shù)拓展,如果API只要求傳頁碼序號,那這個類可以不用實現(xiàn),直接用基類ListServiceReformerImpl即可。

HouseSourceListDomainServiceImpl是必須實現(xiàn)的類,負責(zé)模塊的數(shù)據(jù)獲取,也是框架中數(shù)據(jù)請求層要實現(xiàn)要替換的類。請求邏輯比較特別時,這個類可以完全重寫,比如這個例子的數(shù)據(jù)請求,它不同于一般列表只從一個API獲取數(shù)據(jù),它要從兩個API根據(jù)情況獲取和拼接數(shù)據(jù),獲取到數(shù)據(jù)后的結(jié)果處理邏輯很不同,所以要完全重新寫一個實現(xiàn)類,而不是繼承一個ListDomainServiceImpl的基類(下面會講到)利用里面通用的結(jié)果處理邏輯。不過,哪怕數(shù)據(jù)請求層的邏輯發(fā)生多么天翻復(fù)地的變化,只要這些數(shù)據(jù)仍然要以列表的方式用表格視圖展示,那么這個框架就還是適用于它,那么它的變動還是只停留在數(shù)據(jù)請求層的這個實現(xiàn)類,不會影響到上層代碼,我們上面構(gòu)建的協(xié)議的交互還是成立的。由于沒有基類繼承,HouseSourceListDomainServiceImpl要實現(xiàn)所有從數(shù)據(jù)請求到結(jié)果封裝傳遞的邏輯,代碼量會比較大。

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


@interface HouseSourceListDomainServiceImpl()

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

@end

@implementation HouseSourceListDomainServiceImpl

#pragma mark - Reformer

/// 下面代表了一大段的ListDomainService協(xié)議實現(xiàn)代碼

///  要實現(xiàn)初始化方法
- (id)initWithReformer:(id<ListServiceReformer>)reformer{
    if (self = [super init]) {
        self.reformer = reformer;
        //一定要依賴reformer,否則就返回nil
        if (!self.reformer) {
            return nil;
        }
    }
    return self;
}

- (void)doService{
    ///  要實現(xiàn)doService代理方法
}

- (void)serviceCompletion:(void (^)(id<ListServiceReformer>))completion{
    ///  要實現(xiàn)serviceCompletion:代理方法
}

- (id<ListServiceReformer>)reformer{
    ///  要實現(xiàn)reformer代理方法
}

#pragma mark - API

///  下面代表了一大段的API的調(diào)用方法的實現(xiàn)代碼
- (void)requestForSaleOwnerList{

}

- (void)requestForRentOwnerList{
    
}

- (void)requestForSaleQuickMatchList{
   
}

- (void)requestForRentQuickMatchList{
   
}

#pragma mark - Results Process

/// 下面代表了一大段的結(jié)果處理邏輯代碼
- (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

/// 下面代表了一大段的將數(shù)據(jù)由字典轉(zhuǎn)換成model的代碼

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

@end

而如果這個列表是普通的列表,它只處理單個API獲取數(shù)據(jù)的情況,那么這個ListDomainService的實現(xiàn)類就會簡單很多了,因為我做了ListDomainServiceImpl這個基類的封裝,在里面實現(xiàn)了大部分對請求結(jié)果的通用處理邏輯,還增強了其他功能,比如添加了創(chuàng)建測試數(shù)據(jù)功能,方便在接口沒完成時就能測試列表功能。只要繼承了基類,那么實現(xiàn)類的子類就基本只處理一下Model轉(zhuǎn)換的代碼就行了。比如下面我是業(yè)主列表的數(shù)據(jù)請求層實現(xiàn)類,它是繼承了ListDomainServiceImpl的子類,它只需要設(shè)置它自己的model轉(zhuǎn)換邏輯modelTransform,設(shè)置它自己的列表數(shù)據(jù)在response里面的key路徑,調(diào)用自己的API請求方法,然后在請求方法的success的block里面調(diào)用基類的[self doneWithResponse:response]方法,在failure的block里面調(diào)用基類的[self doneWithError:errorMsg]方法,它就能完成作為數(shù)據(jù)請求層實現(xiàn)著的職責(zé)。

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

@interface OwnerHouseSourceManageListDomainServiceImpl()

@end

@implementation OwnerHouseSourceManageListDomainServiceImpl

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

- (void)initialization{
    @weakify(self);
    ///  指定自己的列表數(shù)據(jù)在response里面的key路徑
    self.listKeys = @[@"Data",@"HouseList"];
    
    [self modelTransform:^id(NSDictionary *dic) {
        if (!dic) {
            return nil;
        }
        ///  一段將字典轉(zhuǎn)換為model的代碼,如果沒必要轉(zhuǎn),可以不設(shè)置這個block
        return model;

    }];
    
    [self customService:^{
        @strongify(self);
        ///  調(diào)用自己的API請求方法
        [self exeGetMyHouseList];
    }];
}

#pragma mark - API

- (void)exeGetMyHouseList{
        [api startWithSuccess:{
            ///  已由基類實現(xiàn)的API請求成功后結(jié)果處理方法
            [self doneWithResponse:response];
        }
        failure:{
             ///  已由基類實現(xiàn)的API請求失敗后結(jié)果處理方法
            [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<ListServiceReformer> reformer;
@property (nonatomic, copy) void (^customService)();
@property (nonatomic, copy) void (^completion)(id<ListServiceReformer>);
@property (nonatomic, copy) id (^modelTransform)(NSDictionary *dic);

@end

@implementation ListDomainServiceImpl

#pragma mark - ListDomainService
// 設(shè)置參數(shù)
- (id)initWithReformer:(id<ListServiceReformer>)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;
}

//執(zhí)行服務(wù)
- (void)doService {
    if (!self.reformer) {
        return;
    }
    if (self.customService) {
        self.customService();
    }
}

// 返回結(jié)果
- (void)serviceCompletion:(void (^)(id<ListServiceReformer>))completion {
    self.completion = completion;
}

- (id<ListServiceReformer>)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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,990評論 2 374

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