規範使用AFNetworking

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;
}

這樣做的目的有兩個:

  1. 項目中每個網絡請求比較多,每次都要初始化一次對象和設置的話,不如直接初始化一次,對相關的設置做一次配置之後就可以每次使用了。將httpSessionManager統一的管理起來。

  2. 有關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還對應着不同的請求地址、簽名等方式也不同,所以統一在這個類裏面去處理就好。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章