单次网络请求的实现
网络请求的实现一般分为两种方法
- 通过iOS源生类NSURLConnection或者NSURLSession
- 使用第三方库AFNetworking或者MKNetworkKit等
NSURLConnection与NSURLSession
根据Objective-C.io介绍和官方文档介绍,2013年NSURLSession正式推出,是NSURLConnection的重构版,但是其核心都是实例化一个NSURLRequest传递给NSURLConnection或者NSURLSession。然后异步地返回一个NSURLResponse以及包含服务器返回信息的NSData。
NSURLConnection这个名字,实际上是指一系列有关联的组件:
- NSURLRequest
- NSURLResponse
- NSURLProtocol
- NSURLCache
- NSHTTPCookieStorage
- NSURLCredentialStorage
- 同名类NSURLConnection。
NSURLSession指的也不仅是同名类 NSURLSession,
- NSURLRequest
- NSURLCache
- NSURLSession
- 基类NSURLSessionTask
- 上传下载用的NSURLSessionConfiguration
- 子类NSURLSessionDataTask: NSURLSessionTask
- 子类NSURLSessionUploadTask: NSURLSessionTask
- 子类NSURLSessionDownloadTask: NSURLSessionTask
- NSURLSessionStreamTask
官方文档是这样解释的
NSURLSession—A session object.
NSURLSessionConfiguration—A configuration object used when initializing the session.
NSURLSessionTask—The base class for tasks within a session.
NSURLSessionDataTask—A task for retrieving the contents of a URL as an NSData object
NSURLSessionUploadTask—A task for uploading a file, then retrieving the contents of a URL as an NSData object
NSURLSessionDownloadTask—A task for retrieving the contents of a URL as a temporary file on disk
NSURLSessionStreamTask—A task for establishing a TCP/IP connection
AFNetworking与MKNetworkKit
第三方库使用起来就更加方便,以AFNetworking2.0举例
- (AFHTTPRequestOperation *)POST:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
可以看到一个POST请求被封装成一个接口,然后成功的数据被解析成一个responseObject返回,错误被封装成NSError,所有请求的状态都被封装入AFHTTPRequestOperation.可见,第三放库连NSURLRequest也不用创建,爽的飞起
网络模块的目的
由于在一个App中需要发出的请求不止一条,所以需要一个模块统一管理所有请求,便于维护和更新.因为不可能在每一个需要网络请求的地方,都需要写URL的字符串,然后再调用网络请求实现方法来请求,这样想也知道代码会混乱和一坨屎一样.
网络模块的分层设计
网络模块的设计最主要的一个概念分层,以目前我在使用的设计来讲,一般分为两层
- Service层 (服务层)
- API层 (数据层)
- RequestCore层 (网络请求核心层)
不同层有不同的职能,以Service层来说:
- 直接对外提供网络服务,用户只需要传入参数,就可以获得固定接口的返回值
- 对用户传入的数据以及对全局数据进行路由
- 对数据进行字符串化加解密等简单处理
- 执行API层函数
API层的职能:
- 接收从服务层传来的纯字符串数据
- 确定请求的地址URL,并且把参数进行组装序列化
- 执行RequestCore层核心请求代码
RequestCore层的职能:
- 接收从API层传来的URL地址,参数等
- 执行网络请求,通过NSURLSession或者AFNetworking等不同方法实现
网络模块的接口设计
网络模块的接口设计,为了方便管理和规划,主要用到了以下规则
- 使用头文件和宏(或静态字符串)来管理接口地址(URL)
- Service层和API层全部使用类函数(+)
- 避免存在属性(Property),Service层和API层并不进行实例化
- 使用Category分类来管理不同层的接口函数(API+Category与Service+Category)
- Service层和API层对外通信使用Model,各自有对应的Handler函数进行Model的处理
- 使用全局单例来管理RequestCore
网络模块的通信设计
在对网络模块进行分层规划,以及对每一层的接口样式进行设计之后,层与层直接需要进行通信,也就是数据传输,因为网络请求可能会存在并发和异步,所以说使用什么通讯方法十分关键.目前我接触到的有两种
- 使用闭包(Block)
- 使用通知(Notification)
如果使用闭包,每一个接口函数包含一个闭包,这个闭包是可复用的,所以便于处理和管理.如果使用通知,也需要将每个接口对应的对应的ID,通过KVC的方法动态绑定到一个Notification的userInfor中,使用同一个NotificationID来复用
本人目前使用的网络模块是用闭包Block进行通讯,而一位同事的GitHub中,有使用通知的示例
从底至顶实现一个网络模块
URL接口头文件APIConfigureHeader.h
#ifndef APIConfigureHeader_h
#define APIConfigureHeader_h
#define APIFormat(Head, Version, Function) ([NSString stringWithFormat:@"/%@/%@/%@", Head, Version, Function])
#define API_Version @"V1"
#pragma mark - Environment Configure
//0.生产环境
//1.准生产环境
#define __Environment_Mark__ (0)
#if (__Environment_Mark__ == (0))
//生产环境
#define API_BaseUrl @"https://client.baidu.com.cn" //基础域名
#define API_Login_Head @"account/api/login"//账户模块登录
#elif (__Environment_Mark__ == (1))
//准生产环境
#define API_BaseUrl @"http://client.test.baidu.com.cn" //基础域名
#define API_Login_Head @"app/account/api/login"//账户模块登录
#endif
#pragma mark - API Address Configure
#define API_VerifyCode APIFormat(API_Login_Head, API_Version, @"verifyCode") //验证码
#define API_Login_ByPassword APIFormat(API_Login_Head, API_Version, @"password") //密码登录
#endif //API_NETCONFIGURE_H
RequestCore层
RequestCore层是实际执行网络请求部分,其构造取决于你使用那种设计,可以基于AFNetworking或者源生的URLConnection等.其核心思想如下
- RequestCore本身是个全局单例
- RequestCore负责添加公共参数
- RequestCore负责生成URLRequest
- RequestCore负责发送URLRequest
由于AFNetworking2.0本身就是一个单例,且内部完成以上3-4条,所以可以继承于
@interface NetRequestCore : AFHTTPRequestOperationManager
下面我们来使用源生代码自己实现
RequestCore.h
typedef NS_ENUM(NSInteger, ParametersEncoding) {
ParametersEncodingUrl = 0,
ParametersEncodingJSON
};
typedef void(^FailureBlock)(NSError *error);
typedef void(^SuccessBlock)(NSData *responseData);
@interface RequestCore : NSObject
@property (nonatomic, strong, readonly) NSURL *baseUrl;
#pragma mark - Core Instance
+ (instancetype)shareInstance;
#pragma mark - NSURLRequest
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters ParametersEncoding:(ParametersEncoding)encoding;
#pragma mark - NSURLConnection
- (void)sendReqeust:(NSURLRequest *)request success:(SuccessBlock)success failure:(FailureBlock)failure;
#pragma mark - GET
- (void)get:(NSString *)path parameters:(NSDictionary *)parameters success:(SuccessBlock)success failure:(FailureBlock)failure;
#pragma mark - POST
- (void)post:(NSString *)path withJsonParameters:(NSDictionary *)parameters success:(SuccessBlock)success failure:(FailureBlock)failure;
- (void)post:(NSString *)path withUrlParameters:(NSDictionary *)parameters success:(SuccessBlock)success failure:(FailureBlock)failure;
@end
RequestCore.m
#import "RequestCore.h"
#import "APIConfigureHeader.h"
@implementation RequestCore
#pragma mark - Core Instance
+ (instancetype)shareInstance {
static RequestCore *_shareInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_shareInstance = [[RequestCore alloc] init];
});
return _shareInstance;
}
- (instancetype)init {
if (self = [super init]) {
_baseUrl = [NSURL URLWithString:API_BaseUrl];
}
return self;
}
#pragma mark - Core Tool
+ (NSMutableDictionary *)commonParams {
NSMutableDictionary *commonParams = [NSMutableDictionary dictionary];
commonParams[@"systemType"] = @"ios"; //系统类型
commonParams[@"deviceName"] = @"John"; //终端名称
return commonParams;
}
+ (NSMutableDictionary *)appendInfo:(NSDictionary *)params {
NSMutableDictionary *newParams = [[self class] commonParams];
for (NSString *key in [params allKeys]) {
newParams[key] = params[key];
}
return newParams;
}
- (NSURL *)encodeUrlWithPath:(NSString *)path {
NSString *urlString = [[NSURL URLWithString:path relativeToURL:_baseUrl] absoluteString];
NSString *utf8String = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
return [NSURL URLWithString:utf8String];
}
#pragma mark - NSURLRequest
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters ParametersEncoding:(ParametersEncoding)encoding {
NSURL *URL = [self encodeUrlWithPath:path];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30];
request.HTTPMethod = method;
if (parameters) {
//Get
if ([method isEqualToString:@"GET"] || [method isEqualToString:@"HEAD"] || [method isEqualToString:@"DELETE"]) {
NSMutableArray *array = [NSMutableArray array];
for (NSString *key in parameters.allKeys) {
NSString *string = [NSString stringWithFormat:@"%@=%@", key, parameters[key]];
[array addObject:string];
}
NSString *bodyStr = [array componentsJoinedByString:@"&"];
NSString *urlWithGetParameters = [[URL absoluteString] stringByAppendingFormat:[path rangeOfString:@"?"].location == NSNotFound ? @"?%@" : @"&%@", bodyStr];
request.URL = [NSURL URLWithString:urlWithGetParameters];
}
else {
//POST
switch (encoding) {
case ParametersEncodingUrl:{
[request setValue:@"application/x-www-form-urlencoded; charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
NSMutableArray *array = [NSMutableArray array];
for (NSString *key in parameters.allKeys) {
NSString *string = [NSString stringWithFormat:@"%@=%@", key, parameters[key]];
[array addObject:string];
}
NSString *bodyStr = [array componentsJoinedByString:@"&"];
NSData *bodyData = [bodyStr dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPBody = bodyData;
break;
}
case ParametersEncodingJSON:{
[request setValue:@"application/json;charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
NSData *data = [NSJSONSerialization dataWithJSONObject:parameters options:kNilOptions error:nil];
request.HTTPBody = data;
break;
}
default:
break;
}
}
}
return request;
}
#pragma mark - NSURLConnection
- (void)sendReqeust:(NSURLRequest *)request success:(SuccessBlock)success failure:(FailureBlock)failure{
NSOperationQueue *queue = [NSOperationQueue mainQueue];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
// NSLog(@"connectionError = %@", connectionError);
if (failure) {
failure(connectionError);
}
}
else {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSInteger responseCode = [httpResponse statusCode];
// NSLog(@"response %@", response);
// NSLog(@"responseCode %tu", responseCode);
if (responseCode == 200) {
if (success) {
success(data);
}
}
else {
NSError *error = [NSError errorWithDomain:@"NSURLErrorDomain" code:9999 userInfo:@{@"NSLocalizedDescription":@"接口请求地址或参数错误"}];
if (failure) {
failure(error);
}
}
}
}];
}
#pragma mark - GET
- (void)get:(NSString *)path parameters:(NSDictionary *)parameters success:(SuccessBlock)success failure:(FailureBlock)failure {
NSMutableURLRequest *request = [self requestWithMethod:@"GET" path:path parameters:parameters ParametersEncoding:ParametersEncodingUrl];
[self sendReqeust:request success:success failure:failure];
}
#pragma mark - POST
- (void)post:(NSString *)path withJsonParameters:(NSDictionary *)parameters success:(SuccessBlock)success failure:(FailureBlock)failure {
NSMutableDictionary *combinedParameters = [[self class] appendInfo:parameters];
NSMutableURLRequest *request = [self requestWithMethod:@"POST" path:path parameters:combinedParameters ParametersEncoding:ParametersEncodingJSON];
[self sendReqeust:request success:success failure:failure];
}
- (void)post:(NSString *)path withUrlParameters:(NSDictionary *)parameters success:(SuccessBlock)success failure:(FailureBlock)failure {
NSMutableDictionary *combinedParameters = [[self class] appendInfo:parameters];
NSMutableURLRequest *request = [self requestWithMethod:@"POST" path:path parameters:combinedParameters ParametersEncoding:ParametersEncodingUrl];
[self sendReqeust:request success:success failure:failure];
}
@end
RequestResponse & RequestHadle
- RequestResponse: 把Service层的数据封装成Model返回
- RequestHandle: 用于对不同层的进行数据处理
APIHandle有两个主要功能:
- 将返回的NSData(一般是JSON串)序列化成词典
- 将返回的NSError直接返回
ServiceHandle有主要功能
- 根据API层的返回判断网络请求是否成功
- 请求成功的情况下把NSData的字典封装成Model
RequestResponse.h
@interface RequestResponse : NSObject
@property (assign, nonatomic) BOOL reqStatus; //请求是否成功 YES成功 NO失败
@property (assign, nonatomic) BOOL reqNetError; //是否物理请求失败
@property (strong, nonatomic) id reqData; //二进制数据
@property (copy, nonatomic) NSString *reqDataStr; //二进制数据字符串
@property (copy, nonatomic) NSString *reqCode; //请求Code
@property (copy, nonatomic) NSString *reqMsg; //备注信息
+ (instancetype)newResponse;
@end
RequestResponse.m
#import "RequestResponse.h"
@implementation RequestResponse
+ (instancetype)newResponse{
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (self) {
_reqDataStr = @"";
_reqCode = @"";
_reqMsg = @"";
}
return self;
}
@end
RequestHandle.h
#import "RequestResponse.h"
@interface RequestHandle : NSObject
typedef void (^APIResponseBlock) (id responeObj, NSError *error);//API Block
typedef void (^SerResponseBlock) (RequestResponse *response);//Service Block
#pragma mark - API Layer Handle
+ (void)handleAPIResponseData:(NSData *)responseData error:(NSError *)error block:(APIResponseBlock)block;
#pragma mark - Service Layer Handle
+ (void)handleSerResponseObject:(id)responeObj error:(NSError *)error block:(SerResponseBlock)block;
@end
RequestHandle.m
#import "RequestHandle.h"
@implementation RequestHandle
#pragma mark - API Layer Handle
+ (void)handleAPIResponseData:(NSData *)responseData error:(NSError *)error block:(APIResponseBlock)block{
if (block) {
if (error) {
block(nil, error);
}
else {
NSDictionary *retDic = [self getObjectFromJSONData:responseData];
block(retDic, nil);
}
}
}
#pragma mark - Service Layer Handle
+ (void)handleSerResponseObject:(id)responeObj error:(NSError *)error block:(SerResponseBlock)block{
RequestResponse *response = [RequestResponse newResponse];
//Convert APILayer NSData's Dictionary To RequestResponse Model
if (error) {
//Request Failure
NSLog(@"=== 网络请求失败 === %@ ===", [error localizedDescription]);
response.reqStatus = NO;
}
else {
//Request Success
response.reqStatus = [responeObj[@"successKey"] boolValue];
response.reqCode = responeObj[@"errorCodeKey"];
response.reqMsg = responeObj[@"errorMsgKey"];
response.reqData = responeObj;
response.reqDataStr = [self getJSONStringFromObject:responeObj];
}
if (block) {
//Block Response Model
block(response);
}
}
#pragma mark - Handle Tools
//This Function Can Be Write In NSData's Custom Category
+ (id)getObjectFromJSONData:(NSData *)data {
NSError *error = nil;
id object = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if (error) {
return nil;
}
else {
return object;
}
}
//This Function Can Be Write In NSObject's Custom Category
+ (NSString *)getJSONStringFromObject:(id)Object{
if (![NSJSONSerialization isValidJSONObject:Object]) {
return nil;
}
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:Object options:kNilOptions error:&error];
if (error) {
return nil;
}
else {
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return string;
}
}
@end
API层
API层的职能:
- 接收从服务层传来的纯字符串数据
- 确定请求的地址URL,并且把参数进行组装序列化
- 执行RequestCore层核心请求代码
ReqAPI.h
#import <Foundation/Foundation.h>
#import "RequestHandle.h"
@interface ReqAPI : NSObject
typedef NS_ENUM(NSInteger, PhoneAuthCodeType) {
PhoneAuthCodeTypeLogin = 1, //登陆短信
PhoneAuthCodeTypeOpenAccount = 2, //开户申请短信
};
#pragma mark - Verify Code
+ (void)reqSendPhoneAuthCodeWithProductNo:(NSString *)productNo
type:(PhoneAuthCodeType)type
block:(APIResponseBlock)block;
@end
ReqAPI.m
#import "ReqAPI.h"
#import "APIConfigureHeader.h"
#import "RequestCore.h"
@implementation ReqAPI
#pragma mark - Verify Code
+ (void)reqSendPhoneAuthCodeWithProductNo:(NSString *)productNo type:(PhoneAuthCodeType)type block:(APIResponseBlock)block{
//Combine Parameters
NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[@"productNo"] = productNo;
params[@"businessType"] = [NSString stringWithFormat:@"%ld",(long)type];
//Launch Request
[[RequestCore shareInstance] post:API_VerifyCode withJsonParameters:params success:^(NSData *responseData) {
[RequestHandle handleAPIResponseData:responseData error:nil block:block];
} failure:^(NSError *error) {
[RequestHandle handleAPIResponseData:nil error:error block:block];
}];
}
@end
Service层
Service层职能:
- 直接对外提供网络服务
- 对用户传入的数据以及对全局数据进行路由
- 对数据进行字符串化加解密等简单处理
- 执行API层函数
用户只需要传入参数,就可得到返回的模型RequestResponse
ReqService.h
#import "ReqAPI.h"
@interface ReqService : NSObject
#pragma mark - Verify Code
+ (void)sendPhoneAuthCodeWithProductNo:(NSString *)productNo
type:(PhoneAuthCodeType)type
block:(SerResponseBlock)block;
@end
ReqService.m
@implementation ReqService
#pragma mark - Verify Code
+ (void)sendPhoneAuthCodeWithProductNo:(NSString *)productNo type:(PhoneAuthCodeType)type block:(SerResponseBlock)block{
[ReqAPI reqSendPhoneAuthCodeWithProductNo:productNo type:type block:^(id responseObj, NSError *error) {
[RequestHandle handleSerResponseObject:responseObj error:error block:block];
}];
}
@end
ReqAPI+Category & ReqService+Category
除了公共请求以外,不同模块的请求可以使用Category来管理,这样的好处有以下几点
- 所有接口都可以使用ReService调用,代码维护成本低
- 根据模块API层与Service层一一对应,方便管理
根据网络模块接口设计原则,API层和Service层本身并不存在实例化,全部通过类函数实现
API层的LoginCategory
//ReqAPI+Login.h
+ (void)reqLoginByPwd:(NSString *)productNo
loginPwd:(NSString *)loginPwd
block:(APIResponseBlock)block;
//ReqAPI+Login.m
+ (void)reqLoginByPwd:(NSString *)productNo loginPwd:(NSString *)loginPwd block:(APIResponseBlock)block {
//Combine Parameters
NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[@"productNo"] = productNo;
params[@"password"] = loginPwd;
//Launch Request
[[RequestCore shareInstance] post:API_Login_ByPassword withJsonParameters:params success:^(NSData *responseData) {
[RequestHandle handleAPIResponseData:responseData error:nil block:block];
} failure:^(NSError *error) {
[RequestHandle handleAPIResponseData:nil error:error block:block];
}];
}
Service层的LoginCategory
//ReqService+Login.h
+ (void)loginByPwd:(NSString *)productNo
loginPwd:(NSString *)loginPwd
block:(SerResponseBlock)block;
//ReqService+Login.m
+ (void)loginByPwd:(NSString *)productNo loginPwd:(NSString *)loginPwd block:(SerResponseBlock)block {
[ReqAPI reqLoginByPwd:productNo loginPwd:loginPwd block:^(id responseObj, NSError *error) {
[RequestHandle handleSerResponseObject:responseObj error:error block:block];
}];
}