DRDNetworking笔记
饿了么开源的一个网络库DRDNetworking,基于AFNetworking3.0
- 如果采取集中式的API设计,相对应JSON-RPC会产生大量的RPC协议封装代码。并且对于不同版本、类型的RPC协议,需要有不同的集中函数或者增加大量的参数来处理其中的差异性。
- 如果采取分布式的API设计,则可以将这部分协议代码放进API自身类中来进行处理。在这里,我设计了一个
RPCProtocol
,由业务方自己来定义所需要遵循的业务RPC标准。而每个API都保存一个rpcDelegate
字段来自定义自己的上层协议,而如果为空时,即代表着不进行RPC封装而是直接发送,从而达到JSON-RPC
和RESTFUL
在一个APP共存的目的;并且由于每个API都可以指定不同的rpcDelegate,因此可以适用于服务器端不同的RPC版本兼容性。
总的来说,并不怎么样啊!– 20170125
JSON-RPC API
NS_ASSUME_NONNULL_BEGIN
@protocol DRDRPCProtocol <NSObject>
- (nullable NSString *)rpcRequestUrlWithAPI:(DRDBaseAPI *)api;
- (nullable id)rpcRequestParamsWithAPI:(DRDBaseAPI *)api;
- (nullable id)rpcResponseObjReformer:(id)responseObject withAPI:(DRDBaseAPI *)api;
- (nullable id)rpcResultWithFormattedResponse:(id)formattedResponseObj withAPI:(DRDBaseAPI *)api;
- (NSError *)rpcErrorWithFormattedResponse:(id)formattedResponseObj withAPI:(DRDBaseAPI *)api;
@end
NS_ASSUME_NONNULL_END
Protocol中会对RequestURL
,RequestParams
进行RPC装箱设计,并且对于回包,也有rpcResponseObjReformer
进行拆箱,将可用值和错误值交给rpcResultWithFormattedResponse
以及rpcErrorWithFormattedResponse
处理后,再返回给业务上层。
通过使用RPCProtocol
,我们保持了整个网络层上层协议的一种可扩展性。
Example
下面的例子中,DRDJsonRpcVersionTwo将请求封装为:
Printing description of request:
<NSMutableURLRequest: 0x7b894d20> { URL: http://www.raboof.com/projects/jayrock/demo.ashx?test }
Printing description of parameters:
{
id = "20dd6cc5-55bd-419f-95b3-77fc6459ccc8";
jsonrpc = "2.0";
method = add;
params = {
a = 12;
b = 1212;
};
}
返回:
Printing description of responseObject:
{
id = "a1bda1d7-22c7-4fbd-a4ba-d5fb1c44a13e";
result = 1224;
}
Code:
DRDGeneralAPI *plusAPI = [[DRDGeneralAPI alloc]init];
plusAPI.baseUrl = @"http://www.raboof.com/projects/jayrock/demo.ashx?test";
plusAPI.requestMethod = @"add";
plusAPI.requestParameters = @{
@"a" : @(a),
@"b" : @(b)
};
/// 使用JSON-RPC ///
plusAPI.rpcDelegate = [DRDJsonRpcVersionTwo sharedJsonRpcVersionTwo];
__weak typeof(self) weakSelf = self;
[plusAPI setApiCompletionHandler:^(NSNumber * responseObject, NSError * error) {
if (error) {
[[[UIAlertView alloc] initWithTitle:@"Error"
message:[NSString stringWithFormat:@"%@", error.localizedDescription]
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil, nil]show];
} else {
weakSelf.labelResult.text = [NSString stringWithFormat:@"%@", responseObject];
}
}];
[plusAPI start];
@interface DRDJsonRpcVersionTwo : NSObject<DRDRPCProtocol>
+ (nullable instancetype)sharedJsonRpcVersionTwo;
@end
static DRDJsonRpcVersionTwo *sharedInstance = nil;
@implementation DRDJsonRpcVersionTwo
+ (instancetype)sharedJsonRpcVersionTwo {
if (!sharedInstance) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[DRDJsonRpcVersionTwo alloc] init];
});
}
return sharedInstance;
}
#pragma mark - Protocol RPC
- (NSString *)rpcRequestUrlWithAPI:(DRDBaseAPI *)api {
if (api.customRequestUrl) {
return api.customRequestUrl;
} else {
return api.baseUrl;
}
}
/// 封装JSON-RPC请求参数 ///
- (id)rpcRequestParamsWithAPI:(DRDBaseAPI *)api {
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
dic[@"jsonrpc"] = @"2.0";
dic[@"id"] = [[[NSUUID UUID] UUIDString] lowercaseString];
if ([api requestMethod]) {
dic[@"method"] = [api requestMethod];
}
if ([api requestParameters]) {
dic[@"params"] = [api requestParameters];
}
NSDictionary *apiAdditionalParams = [api apiAddtionalRPCParams];
[apiAdditionalParams enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL * stop) {
[dic setObject:obj forKey:key];
}];
return [dic copy];
}
- (id)rpcResponseObjReformer:(id)responseObject withAPI:(nonnull DRDBaseAPI *)api {
NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
[((NSDictionary *)responseObject) enumerateKeysAndObjectsWithOptions:NSEnumerationReverse
usingBlock:^(id key, id obj, BOOL * stop) {
if ([key isEqualToString:@"result"] ||
[key isEqualToString:@"error"]) {
if (obj && ![obj isEqual:[NSNull null]]) {
dic[key] = obj;
} else {
dic[key] = [NSNull null];
}
}
}];
return [dic copy];
}
- (id)rpcResultWithFormattedResponse:(id)formattedResponseObj withAPI:(nonnull DRDBaseAPI *)api {
return [((NSDictionary *)formattedResponseObj) objectForKey:@"result"];
}
// 如果需要对某些错误进行统一处理,可以在本方法中进行处理
- (NSError *)rpcErrorWithFormattedResponse:(id)formattedResponseObj withAPI:(nonnull DRDBaseAPI *)api {
id errorInfo = [((NSDictionary *)formattedResponseObj) objectForKey:@"error"];
if (errorInfo && ![errorInfo isEqual:[NSNull null]]) {
return errorInfo;
} else {
return nil;
}
}
@end
SessionManager
由APIManager来负责提供SessionManager的策略(NSCache)。并且通过Global的配置,来决定每个Session的最大并发数。同时将AFNetworking封装进整个APIManager,保持对外透明。这样如果未来如果升级AFNetworking版本,或者打算切换到直接使用NSURLSession来处理网络连接,上层业务API也不需要有任何的改动,进一步增强了未来的可配置性。
/// SessionManager的缓存 ///
AFHTTPSessionManager *sessionManager;
sessionManager = [self.sessionManagerCache objectForKey:baseUrlStr];
if (!sessionManager) {
sessionManager = [self newSessionManagerWithBaseUrlStr:baseUrlStr];
[self.sessionManagerCache setObject:sessionManager forKey:baseUrlStr];
}
/// Session的最大并发数控制 ///
- (AFHTTPSessionManager *)newSessionManagerWithBaseUrlStr:(NSString *)baseUrlStr {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration ephemeralSessionConfiguration];
if (self.configuration) {
sessionConfig.HTTPMaximumConnectionsPerHost = self.configuration.maxHttpConnectionPerHost;
} else {
sessionConfig.HTTPMaximumConnectionsPerHost = MAX_HTTP_CONNECTION_PER_HOST;
}
return [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:baseUrlStr]
sessionConfiguration:sessionConfig];
}
检查用户频繁发送同一个请求,使用的错误提示文字
if ([self.sessionTasksCache objectForKey:hashKey]) {
NSString *errorStr = self.configuration.frequentRequestErrorStr; // 频繁发送同一个请求提示
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey : errorStr
};
NSError *cancelError = [NSError errorWithDomain:NSURLErrorDomain
code:NSURLErrorCancelled
userInfo:userInfo];
[self callAPICompletion:api obj:nil error:cancelError];
if (completionGroup) {
dispatch_group_leave(completionGroup);
}
return;
}