AF是iOS中一個非常優秀的網絡請求框架,下面從我個人的角度來對AF的使用做一個規範。
文章目錄
背景
很早以前或許你還用過ASIHTTPRequest,後來就大都使用AFNetworking了,AF採用block的形式將請求結果返回,這樣管理起來我感覺比ASI更加便捷。
一直使用AF,但是從來沒有對AF有一個規範化的使用。經常是一個請求類裏面即有AF的代碼又有業務代碼,搞得亂七八糟。
結構圖
回頭補上…
核心的類
本着儘量業務解耦的原則和功能單一性原則,分爲三類規範AF的使用。
第一,與AF框架對接的類;第二,項目裏的網絡請求的管理類;第三,協助第二部分進行業務處理的類。
1. 與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
1.1 管理httpSessionManager
我們使用AF的時候直接使用AFHTTPSessionManager這個類發起網絡請求。
創建一個類來管理httpSessionManager對象,我們將httpSessionManager處理成全局的單例對象
+ (AFHTTPSessionManager *)httpSessionManager {
if (httpSessionManager == nil) {
//...
}
return httpSessionManager;
}
這樣做的目的有兩個:
-
項目中每個網絡請求比較多,每次都要初始化一次對象和設置的話,不如直接初始化一次,對相關的設置做一次配置之後就可以每次使用了。將httpSessionManager統一的管理起來。
-
有關AF使用檢測發現內存問題的解決,爲什麼會檢測出內存問題,這個不是AF的問題,這裏不贅述了,看這裏。
1.2 提供AP對外的API
#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
2. 發起類
業務使用該類發起網絡請求
先來看一下完整的代碼:
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
這個類有以下幾個作用:
2.1 向業務提供網絡請求的API
向外提供了GET、POST請求的API和取消網絡任務的API,這都是基礎功能。
這裏以一個GET請求爲例,業務層使用ENHTTPRequestManager這個類直接發起網絡請求。
請求內部是對參數等的處理,這些處理,我們藉助另一個類,ENHTTPHelper,在這個類裏面處理所有的參數拼接等的事情。
2.2 管理網絡請求的task任務
我們用taskSet來管理所有的網絡請求的task,發起任務,任務完成/取消,刪除任務對象。
管理任務的目的很簡單,就是爲了能夠在想取消任務的時候直接取消。這種業務場景適用在界面退出前,先取消正在進行中的請求。
2.3 髮網絡請求,處理請求結果
發起網絡請求,處理一下返回的data,將數據轉成json,將json轉成字典返回出去。
3. 協助類
協助發起類處理一些其他事宜
協助類提供放一些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還對應着不同的請求地址、簽名等方式也不同,所以統一在這個類裏面去處理就好。