【ViewModel、AppService和DomainService怎么分工協作】
前面說到ViewModel把很多事情代理給了AppService去做,其實還有另外一部分事情它代理給DomainService去做了,這里說的AppService和DomainService不一定是一個具體的類,它們可以是幾個這種類型的類,或者它們可以是這種類型的類的協議,而不是具體實現類。這里我沒有說應該怎么怎么做,因為我在寫ViewModel的過程中也存在這些類的各種組合情況,沒有固定的模式。不過,在我慢慢探索的過程中,我還是發現了可能遵循以下原則會比較好:
- 依賴協議而不是依賴實現。
- 要分層,每層只做自己該做的事。
- 盡量不要跨層交互。
分層的好處是明顯的,如果分層的職責分配的合理,分析代碼時會很清晰,哪一層的代碼問題去哪一層找;有利于隔離變化,保持代碼最大程度的重用,當移植時,變化到哪一層就替換哪一層,其他不變的層便是可以重用的。
依賴協議而不依賴實現便是一種分層所仰賴的重要技術,有了它分層才能實現替換變化的代碼,保持不變的代碼被重用。
每層只做自己該做的事,盡量不要跨層交互,就是分層的重要基礎,沒有按照這個原則去分層,那么也不可能實現恰當的隔離變化層次用于代替,保持其他部分層次的穩定重用。
下面用“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代表的大腦層(控制層)(中樞層)的職責:
- 接受表現層(Controller/View)提供的信息,改變自己的狀態,反饋給表現層。
- 接受表現層傳遞的交互,執行對交互的響應命令,改變自己的狀態,反饋給表現層。
直接通過屬性從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 <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服務層的職責
如果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 <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
小結:
如果用一個比喻來闡述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 <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, DomainServiceStatusType) {
DomainServiceStatusSuccess = 1,
DomainServiceStatusNoData,
DomainServiceStatusNetworkFailure,
DomainServiceStatusOnGoing,
DomainServiceStatusPaused,
DomainServiceStatusCanceled,
DomainServiceStatusNoExecute
};
@protocol ListServiceReformer <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
/// ListServiceReformerImpl.h
#import "ListServiceReformer.h"
#import <Foundation/Foundation.h>
@interface ListServiceReformerImpl : NSObject <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.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 <Foundation/Foundation.h>
@protocol ListDomainService <NSObject>
// 返回初始化service的那個reformer
- (id<ListServiceReformer>)reformer;
// 設置參數
- (id)initWithReformer:(id<ListServiceReformer>)reformer;
// 執行請求
- (void)doService;
// 返回結果
- (void)serviceCompletion:(void (^)(id<ListServiceReformer>))completion;
@end
四,固化上層和數據請求層的交互邏輯
這個設計是做項目的時候一面做,一面想出來的,做的匆忙,有些不完善的地方,比如這里的交互中介其實沒有把上層和數據請求層隔離開,而只是作為一個數據的傳遞者和緩存者,上層和數據請求層是直接交互的。TableViewManager基類通過屬性引入了DomainService; 當需要執行數據請求是直接調用它的doService方法;在設置DomainService實現類對象時,通過在它的serviceCompletion:方法設置block來監聽不同請求狀態并設置不同響應。
/// 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:{
/// 請求成功拿到數據后的操作
}
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
=============================實現類之間如何工作============================
只要是要展示列表數據的模塊就適合利用這個迷你框架。下面舉例房源詳情的資訊列表。首先模塊要創建一系列相關的子類或實現類,實現類的命名要能暗示它們的作用:
由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 <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設為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<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和數據操作后,才執行子類自定義的操作
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<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
/// 這些數據封裝的方法也是依賴于由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<ListServiceReformer>);
@end
@implementation HouseSourceListDomainServiceImpl
#pragma mark - Reformer
/// 下面代表了一大段的ListDomainService協議實現代碼
/// 要實現初始化方法
- (id)initWithReformer:(id<ListServiceReformer>)reformer{
if (self = [super init]) {
self.reformer = reformer;
//一定要依賴reformer,否則就返回nil
if (!self.reformer) {
return nil;
}
}
return self;
}
- (void)doService{
/// 要實現doService代理方法
}
- (void)serviceCompletion:(void (^)(id<ListServiceReformer>))completion{
/// 要實現serviceCompletion:代理方法
}
- (id<ListServiceReformer>)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<ListServiceReformer>)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<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
// 設置參數
- (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;
}
//執行服務
- (void)doService {
if (!self.reformer) {
return;
}
if (self.customService) {
self.customService();
}
}
// 返回結果
- (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