文章地址html
网络层做为App
架构中相当重要的中间件之一,承担着业务封装和核心层网络请求交互的职责。讨论请求中间件实现方案的意义在于中间件要如何设计以便减小对业务对接的影响;明晰请求流程中的职责以便写出更合理的代码等。所以在讲如何去设计请求中间件时,主要考虑三个问题:面试
根据暴露给业务层请求API
的不一样,能够分为集约式请求
和离散型请求
两类。集约式请求
对外只提供一个类用于接收包括请求地址、请求参数在内的数据信息,以及回调处理(一般使用block
)。而离散型请求
对外提供通用的扩展接口完成请求json
考虑到AFNetworking
基本成为了iOS
的请求标准,以传统的集约式请求代码为例:数组
/// 请求地址和参数组装
NSString *domain = [SLNetworkEnvironment currentDomain];
NSString *url = [domain stringByAppendingPathComponent: @"getInterviewers"];
NSDictionary *params = @{
@"page": @1,
@"pageCount": @20,
@"filterRule": @"work-years >= 3"
};
/// 构建新的请求对象发起请求
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager POST: url parameters: params success: ^(NSURLSessionDataTask *task, id responseObject) {
/// 请求成功处理
if ([responseObject isKindOfClass: [NSArray class]]) {
NSArray *result = [responseObject bk_map: ^id(id obj) {
return [[SLResponse alloc] initWithJSON: obj];
}];
[self reloadDataWithResponses: result];
} else {
SLLog(@"Invalid response object: %@", responseObject);
}
} failure: ^(NSURLSessionDataTask *task, NSError *error) {
/// 请求失败处理
SLLog(@"Error: %@ in requesting %@", error, task.currentRequest.URL);
}];
/// 取消存在的请求
[self.currentRequestManager invalidateSessionCancelingTasks: YES];
self.currentRequestManager = manager;
复制代码
这样的请求代码存在这些问题:markdown
block
回调潜在的引用问题在业务封装的层面上,应该只关心什么时候发起请求
和展现请求结果
。设计上,请求中间件应当只暴露必要的参数property
,隐藏请求过程和返回数据的处理网络
和集约式请求不一样,对于每个请求API
都会有一个manager
来管理。在使用manager
的时候只须要建立实例,执行一个相似load
的方法,manager
会自动控制请求的发起和处理:架构
- (void)viewDidLoad {
[super viewDidLoad];
self.getInterviewerApiManager = [SLGetInterviewerApiManager new];
[self.getInterviewerApiManager addDelegate: self];
[self.getInterviewerApiManager refreshData];
}
复制代码
集约式请求和离散型请求最终的实现方案并非互斥的,从底层请求的具体行为来看,最终都有统一执行的步骤:域名拼凑
、请求发起
、结果处理
等。所以从设计上来讲,使用基类来统一这些行为,再经过派生生成针对不一样请求API
的子类,以便得到具体请求的灵活性:dom
@protocol SLBaseApiManagerDelegate
- (void)managerWillLoadData: (SLBaseApiManager *)manager;
- (void)managerDidLoadData: (SLBaseApiManager *)manager;
@end
@interface SLBaseApiManager : NSObject
@property (nonatomic, readonly) NSArray<id<SLBaseApiManagerDelegate>) *delegates;
- (void)loadWithParams: (NSDictionary *)params;
- (void)addDelegate: (id<SLBaseApiManagerDelegate>)delegate;
- (void)removeDelegate: (id<SLBaseApiManagerDelegate>)delegate;
@end
@interface SLBaseListApiManager : SLBaseApiManager
@property (nonatomic, readonly, assign) BOOL hasMore;
@property (nonatomic, readonly, copy) NSArray *dataList;
- (void)refreshData;
- (void)loadMoreData;
@end
复制代码
离散型请求的一个特色是,将相同的请求逻辑抽离出来,统一行为接口。除了请求行为以外的行为,包括请求数据解析、重试控制、请求是否互斥等行为,每个请求API
都有单独的manager
进行定制,灵活性更强。另外经过delegate
统一回调行为,减小debug
难度,避免了block
方式潜在的引用问题等异步
在一次完整的fetch
数据过程当中,数据能够分为四种形态:工具
Data
AFN
等工具拉取的数据,通常是JSON
JSON
转换而来,称做Entity
Text
这四种数据形态的流动结构以下:
Server AFN controller view
------------- ------------- ------------- -------------
| | | | | | convert | |
| Data | ---> | JSON | ---> | Entity | ---> | Text |
| | | | | | | |
------------- ------------- ------------- -------------
复制代码
普通状况下,第三方请求库会以JSON
的形态交付数据给业务方。考虑到客户端与服务端的命名规范、以及可能存在的变动,多数状况下客户端会对JSON
数据加工成具体的Entity
数据实体,而后使用容器类保存。从上图的四种数据形态来讲,若是中间件必须选择其中一种形态交付给业务层,Entity
应该是最合理的交付数据形态,缘由有三:
JSON
,业务封装必须完成JSON -> Entity
的转换,多数时候请求发起的业务在C
层中,而这些逻辑老是形成Fat Controller
的缘由Entity -> Text
涉及到了具体的上层业务,请求中间件不该该向上干涉。在JSON -> Entity
的转换过程当中,Entity
已经组装了业务封装最须要的数据内容另外一个有趣的问题是Entity
描述的是数据流动的阶段状态,而非具体数据类型。打个比方,Entity
不必定非得是类对象实例,只要Entity
遵照业务封装的读取规范,能够是instance
也能够是collection
,好比一个面试者Entity
只要能提供姓名
和工做年限
这两个关键数据便可:
/// 抽象模型
@interface SLInterviewer : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) CGFloat workYears;
@end
SLInterviewer *interviewer = entity;
NSLog(@"The interviewer name: %@ and work-years: %g", interviewer.name, interviewer.workYears);
/// 键值约定
extern NSString *SLInterviewerNameKey;
extern NSString *SLInterviewerWorkYearsKey;
NSDictionary *interviewer = entity;
NSLog(@"The interviewer name: %@ and work-years: %@", interviewer[SLInterviewerNameKey], interviewer[SLInterviewerWorkYearsKey]);
复制代码
若是让集约式请求的中间件交付Entity
数据,JSON -> Entity
的形态转换可能会致使请求中间件涉及到具体的业务逻辑中,所以在实现上须要提供一个parser
来完成这一过程:
@protocol EntityParser
- (id)parseJSON: (id)JSON;
@end
@interface SLIntensiveRequest : NSObject
@property (nonatomic, strong) id<EntityParser> parser;
- (void)GET: (NSString *)url params: (id)params success: (SLSuccess)success failure: (SLFailure)failure;
@end
复制代码
而相较之下,离散型请求中BaseManager
承担了统一的请求行为,派生的manager
彻底能够直接将转换的逻辑直接封装起来,无需额外的Parser
,惟一须要考虑的是Entity
的具体实体对象是否须要抽象模型来表达:
@implementation SLGetInterviewerApiManager
/// 抽象模型
- (id)entityFromJSON: (id)json {
if ([json isKindOfClass: [NSDictionary class]]) {
return [SLInterviewer interviewerWithJSON: json];
} else {
return nil;
}
}
- (void)didLoadData {
self.dataList = self.response.safeMap(^id(id item) {
return [self entityFromJSON: item];
}).safeMap(^id(id interviewer) {
return [SLInterviewerInfo infoWithInterviewer: interviewer];
});
if ([_delegate respondsToSelector: @selector(managerDidLoadData:)]) {
[_delegate managerDidLoadData: self];
}
}
/// 键值约定
- (id)entityFromJSON: (id)json keyMap: (NSDictionary *)keyMap {
if ([json isKindOfClass: [NSDictionary class]]) {
NSDictionary *dict = json;
NSMutableDictionary *entity = @{}.mutableCopy;
for (NSString *key in keyMap) {
NSString *entityKey = keyMap[key];
entity[entityKey] = dict[key];
}
return entity.copy;
} else {
return nil;
}
}
@end
复制代码
甚至再进一步,manager
能够同时交付Text
和Entity
这两种数据形态,使用parser
能够对C
层完成隐藏数据的转换过程:
@protocol TextParser
- (id)parseEntity: (id)entity;
@end
@interface SLInterviewerTextContent : NSObject
@property (nonatomic, readonly) NSString *name;
@property (nonatomic, readonly) NSString *workYear;
@property (nonatomic, readonly) SLInterviewer *interviewer;
- (instancetype)initWithInterviewer: (SLInterviewer *)interviewer;
@end
@implementation SLInterviewerTextParser
- (id)parseEntity: (SLInterviewer *)entity {
return [[SLInterviewerTextContent alloc] initWithInterviewer: entity];
}
@end
复制代码
是否须要统一接口的请求封装层
在App
中的请求分为三类:GET
、POST
和UPLOAD
,在不考虑进行封装的状况下,核心层的请求接口至少须要三种不一样的接口来对应这三种请求类型。此外还要考虑核心层的请求接口一旦发生变更(例如AFN
在更新至3.0
的时候修改了请求接口),所以对业务请求发起方来讲,存在一个封装的请求中间层能够有效的抵御请求接口改动的风险,以及有效的减小代码量。上文能够看到对业务层暴露的中间件manager
的做用是对请求的行为进行统一,但并不干预请求的细节,所以manager
也能被当作是一个请求发起方,那么在其下层须要有暴露统一接口的请求封装层:
-------------
中间件 | Manager |
-------------
↓
↓
-------------
请求层 | Request |
-------------
↓
↓
-------------
核心请求 | CoreNet |
-------------
复制代码
封装请求层的问题在于如何只暴露一个接口来适应多种状况类型,一个方法是将请求内容抽象成一系列的接口协议,Request
层根据接口返回参数调度具体的请求接口:
/// 协议接口层
enum {
SLRequestMethodGet,
SLRequestMethodPost,
SLRequestMethodUpload
};
@protocol RequestEntity
- (int)requestMethod; /// 请求类型
- (NSString *)urlPath; /// 提供域名中的path段,以便组装:xxxxx/urlPath
- (NSDictionary *)parameters; /// 参数
@end
extern NSString *SLRequestParamPageKey;
extern NSString *SLRequestParamPageCountKey;
@interface RequestListEntity : NSObject<RequestEntity>
@property (nonatomic, assign) NSUInteger page;
@property (nonatomic, assign) NSUInteger pageCount;
@end
/// 请求层
typedef void(^SLRequestComplete)(id response, NSError *error);
@interface SLRequestEngine
+ (instancetype)engine;
- (void)sendRequest: (id<RequestEntity>)request complete: (SLRequestComplete)complete;
@end
@implementation SLRequestEngine
- (void)sendRequest: (id<RequestEntity>)request complete: (SLRequestComplete)complete {
if (!request || !complete) {
return;
}
if (request.requestMethod == SLRequestMethodGet) {
[self get: request complete: complete];
} else if (request.requestMethod == SLRequestMethodPost) {
[self post: request complete: complete];
} else if (request.requestMethod == SLRequestMethodUpload) {
[self upload: request complete: complete];
}
}
@end
复制代码
这样一来,当有新的请求API
时,建立对应的RequestEntity
和Manager
类来处理请求。对于业务上层来讲,整个请求过程更像是一个异步的fetch
流程,一个单独的manager
负责加载数据并在加载完成时回调。Manager
也不用了解具体是什么请求,只须要简单的配置参数便可,Manager
的设计以下:
@interface WSBaseApiManager : NSObject
@property (nonatomic, readonly, strong) id data;
@property (nonatomic, readonly, strong) NSError *error; /// 请求失败时不为空
@property (nonatomic, weak) id<WSBaseApiManagerDelegate> delegate;
@end
@interface WSBaseListApiManager : NSObject
@property (nonatomic, assign) BOOL hasMore;
@property (nonatomic, readonly, copy) NSArray *dataList;
@end
@interface SLGetInterviewerRequest: RequestListEntity
@end
@interface SLGetInterviewerManager : WSBaseListApiManager
@end
@implementation SLGetInterviewerManager
- (void)loadWithParams: (NSDictionary *)params {
SLGetInterviewerRequest *request = [SLGetInterviewerRequest new];
request.page = [params[SLRequestParamPageKey] unsignedIntegerValue];
request.pageCount = [params[SLRequestParamPageCountKey] unsignedIntegerValue];
[[SLRequestEngine engine] sendRequest: request complete: ^(id response, NSError *error){
/// do something when request complete
}];
}
@end
复制代码
最终请求结构:
-------------
业务层 | Client |
-------------
↓
↓
-------------
中间件 | Manager |
-------------
↓
↓
-------------
| Request |
-------------
↓
↓
请求层 -----------------------------------
↓ ↓ ↓
↓ ↓ ↓
------------- ------------- -------------
| GET | | POST | | Upload |
------------- ------------- -------------
↓ ↓ ↓
↓ ↓ ↓
---------------------------------------------
核心请求 | CoreNet |
---------------------------------------------
复制代码