AF是iOS中一个非常优秀的网络请求框架,下面从我个人的角度来对AF的使用做一个规范。
很早以前或许你还用过ASIHTTPRequest,后来就大都使用AFNetworking了,AF采用block的形式将请求结果返回,这样管理起来我感觉比ASI更加便捷。
一直使用AF,但是从来没有对AF有一个规范化的使用。经常是一个请求类里面即有AF的代码又有业务代码,搞得乱七八糟。
回头补上…
本着尽量业务解耦的原则和功能单一性原则,分为三类规范AF的使用。
第一,与AF框架对接的类;第二,项目里的网络请求的管理类;第三,协助第二部分进行业务处理的类。
通过此类管理AF,这个类是一个桥梁。
项目中发起网络请求的类不要直接去引用AF,因为此类里面会有业务代码,再加上AF的代码的话,就会出现功能一个类承担,杂糅、乱糟糟的情况。
首先我们需要一个”桥梁“来对接业务代码与AF。创建一个管理AFHTTPSessionManager的类,架起AF与业务类之间的桥梁。
这个类的主要作用有两个,1. 管理httpSessionManager 2. 对业务提供AF的请求接口。
#import "ENHTTPSessionManager.h"
#import "AFHTTPSessionManager.h"
static AFHTTPSessionManager *httpSessionManager = nil;
@implementation ENHTTPSessionManager
+ (AFHTTPSessionManager *)httpSessionManager {
if (httpSessionManager == nil) {
// httpSessionManager
httpSessionManager = [[AFHTTPSessionManager alloc] init];
// request out time set
httpSessionManager.requestSerializer.timeoutInterval = 20.f;
// response serializer set
AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer];
responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html", @"text/xml", @"text/plain", nil];
httpSessionManager.responseSerializer = responseSerializer;
// none securityPolicy
httpSessionManager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
}
return httpSessionManager;
}
+ (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure
{
return [[ENHTTPSessionManager httpSessionManager] POST:URLString parameters:parameters headers:nil progress:uploadProgress success:success failure:failure];
}
+ (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure
{
return [[ENHTTPSessionManager httpSessionManager] GET:URLString parameters:nil headers:nil progress:downloadProgress success:success failure:failure];
}
@end
我们使用AF的时候直接使用AFHTTPSessionManager这个类发起网络请求。
创建一个类来管理httpSessionManager对象,我们将httpSessionManager处理成全局的单例对象
+ (AFHTTPSessionManager *)httpSessionManager {
if (httpSessionManager == nil) {
//...
}
return httpSessionManager;
}
这样做的目的有两个:
项目中每个网络请求比较多,每次都要初始化一次对象和设置的话,不如直接初始化一次,对相关的设置做一次配置之后就可以每次使用了。将httpSessionManager统一的管理起来。
有关AF使用检测发现内存问题的解决,为什么会检测出内存问题,这个不是AF的问题,这里不赘述了,看这里。
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// This class is a manager of the HTTP request, there will use AFNetworking send a request in this class. Never add the App business code in this class.
@interface ENHTTPSessionManager : NSObject
+ (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
+ (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
@end
业务使用该类发起网络请求
先来看一下完整的代码:
ENHTTPRequestManager.h
#import <Foundation/Foundation.h>
#import "ENHTTPHelper.h"
NS_ASSUME_NONNULL_BEGIN
/// Ues this class to send the http request and manager the request task.
@interface ENHTTPRequestManager : NSObject
// init
+ (instancetype)manager;
// sen a request for GET
- (void)GETWithAPICommand:(ENAPICommand)command
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *progress))progressCallback
success:(nullable void (^)(NSDictionary *reponse))successCallback
failure:(nullable void (^)(NSError *error))failureCallback;
// the requesting task set.
@property (nonatomic, strong, readonly) NSMutableSet *taskSet;
// cancel a request, cancel the request when dismiss before controller.
- (void)cancelTask:(ENAPICommand)command;
@end
NS_ASSUME_NONNULL_END
ENHTTPRequestManager.m
#import "ENHTTPRequestManager.h"
#import "ENHTTPSessionManager.h"
@interface ENHTTPRequestManager ()
@property (nonatomic, strong, readwrite) NSMutableSet *taskSet;
@end
static ENHTTPRequestManager *_manager = nil;
@implementation ENHTTPRequestManager
- (NSMutableSet *)taskSet {
if (!_taskSet) {
_taskSet = [[NSMutableSet alloc] init];
}
return _taskSet;
}
+ (instancetype)manager {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_manager = [[ENHTTPRequestManager alloc] init];
});
return _manager;
}
- (void)GETWithAPICommand:(ENAPICommand)command
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *progress))progressCallback
success:(nullable void (^)(NSDictionary *reponse))successCallback
failure:(nullable void (^)(NSError *error))failureCallback
{
__weak typeof(self)weakSelf = self;
NSString *URLString = [ENHTTPHelper GETURLStringWithAPICommand:command parameters:parameters];
#ifdef DEBUG
NSLog(@"GET REQUEST : %@",URLString);
#else
#endif
NSURLSessionDataTask *task = [ENHTTPSessionManager GET:URLString progress:progressCallback success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSString *jsonString = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *err;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err];
dispatch_async(dispatch_get_main_queue(), ^{
#ifdef DEBUG
NSLog(@"GET RESPONSE : %@",dict);
#else
#endif
if (successCallback) {
successCallback(dict);
}
[weakSelf removeTask:task];
});
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
dispatch_async(dispatch_get_main_queue(), ^{
#ifdef DEBUG
NSLog(@"GET ERROR : %@",error);
#else
#endif
if (failureCallback) {
failureCallback(error);
}
[weakSelf removeTask:task];
});
}];
NSString *taskDescription = [ENHTTPHelper APIWithAPICommand:command];
if (taskDescription) {
task.taskDescription = taskDescription;
[self.taskSet addObject:task];
}
}
#pragma mark - task manager
- (void)cancelTask:(ENAPICommand)command
{
NSString *taskDescription = [ENHTTPHelper APIWithAPICommand:command];
for (NSURLSessionDataTask *task in self.taskSet) {
if ([task.taskDescription isEqualToString:taskDescription]) {
if (task.state < NSURLSessionTaskStateCanceling) {
[task cancel];
}
[self.taskSet removeObject:task];
}
}
}
- (void)removeTask:(NSURLSessionDataTask *)task
{
if ([self.taskSet containsObject:task]) {
[self.taskSet removeObject:task];
#ifdef DEBUG
NSLog(@"TASK REMOVE : %@",task.taskDescription);
#else
#endif
}
}
- (void)addTask:(NSURLSessionDataTask *)task
{
[self.taskSet addObject:task];
}
@end
这个类有以下几个作用:
向外提供了GET、POST请求的API和取消网络任务的API,这都是基础功能。
这里以一个GET请求为例,业务层使用ENHTTPRequestManager这个类直接发起网络请求。
请求内部是对参数等的处理,这些处理,我们借助另一个类,ENHTTPHelper,在这个类里面处理所有的参数拼接等的事情。
我们用taskSet来管理所有的网络请求的task,发起任务,任务完成/取消,删除任务对象。
管理任务的目的很简单,就是为了能够在想取消任务的时候直接取消。这种业务场景适用在界面退出前,先取消正在进行中的请求。
发起网络请求,处理一下返回的data,将数据转成json,将json转成字典返回出去。
协助发起类处理一些其他事宜
协助类提供放一些API的定义,协助发起类处理一些其他事宜,例如定义Api,GET请求拼接URL等的工作。
ENHTTPHelper.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, ENAPICommand) {
...
};
/// This class assists ENHTTPRequestManager to construct a complete network request. There will more App business in this class.
@interface ENHTTPHelper : NSObject
// Get the request URL for GET request
+ (NSString *)GETURLStringWithAPICommand:(ENAPICommand)command
parameters:(NSDictionary *)parameters;
// Get the API description by API command
+ (NSString *)APIWithAPICommand:(ENAPICommand)command;
@end
ENHTTPHelper.m
#import "ENHTTPHelper.h"
@interface ENHTTPHelper ()
@property (nonatomic, strong, readwrite) NSDictionary *commonParameters;
@end
@implementation ENHTTPHelper
+ (NSString *)GETURLStringWithAPICommand:(ENAPICommand)command
parameters:(NSDictionary *)parameters
{
NSMutableDictionary *allParameters = [NSMutableDictionary dictionary];
// protocol. http / https
NSString *protocol = [ENHTTPHelper protocol];
// domain. xxx.xxx.com
NSString *domain = [ENHTTPHelper domainWithAPICommand:command];
// path
NSString *URLSubpath = [ENHTTPHelper pathWithAPICommand:command];
// common parameters
NSMutableDictionary *commonParameters = [ENHTTPHelper commonParametersWithAPICommand:command];
// other parameters
if (ENSettings.mainSettings.location && ENSettings.mainSettings.location.length) {
commonParameters[@"LocationID"] = ENSettings.mainSettings.location;
}
if (ENSettings.mainSettings.sessionID && ENSettings.mainSettings.sessionID.length) {
commonParameters[@"SessionID"] = ENSettings.mainSettings.sessionID;
}
// add all parameters
[allParameters addEntriesFromDictionary:parameters];
[allParameters addEntriesFromDictionary:commonParameters];
// structure queryItems
NSMutableArray *queryItems = [NSMutableArray arrayWithCapacity:allParameters.count];
for(NSString *name in allParameters)
{
NSString *value = allParameters[name];
NSURLQueryItem *item = [NSURLQueryItem queryItemWithName:name value:value];
[queryItems addObject:item];
}
// structure urlComponents
NSURLComponents *urlComponents = [[NSURLComponents alloc] init];
urlComponents.scheme = protocol;
urlComponents.host = domain;
urlComponents.path = URLSubpath;
urlComponents.queryItems = queryItems;
// return urlComponents' URL string
return urlComponents.URL.absoluteString;
}
#pragma mark -
+ (NSString *)protocol
{
...
}
+ (NSString *)domainWithAPICommand:(ENAPICommand)command
{
...
}
+ (NSString *)pathWithAPICommand:(ENAPICommand)command
{
...
}
+ (NSString *)APITokenWithAPICommand:(ENAPICommand)command
{
...
}
+ (NSMutableDictionary *)commonParametersWithAPICommand:(ENAPICommand)command
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[@"APIToken"] = [ENHTTPHelper APITokenWithAPICommand:command];
dict[@"APICommand"] = [ENHTTPHelper APIWithAPICommand:command];
dict[@"UserID"] = [ENHTTPHelper userIDWithAPICommand:command];
dict[@"Version"] = [ENSettings mainSettings].version;
dict[@"r"] = [ENTools timeStamp];
return dict;
}
+ (NSString *)userIDWithAPICommand:(ENAPICommand)command
{
...
}
+ (NSString *)APIWithAPICommand:(ENAPICommand)command
{
switch (command)
{
...
}
}
@end
ENAPICommand是将API定义成立一个枚举,提供了通过枚举获取对应API的接口,或者项目里直接使用宏都是可以的。
这个类的实现根据不同业务去提供不同API供请求类使用。我这里的业务比较繁琐,不同的API还对应着不同的请求地址、签名等方式也不同,所以统一在这个类里面去处理就好。