現在項目處于維護階段,以前的一部分代碼隨著用戶量的增加,不斷暴露出問題,特別是網絡請求--回調處理方面的修改比較多。每次修改對應的接口都要跳到對應控制器啊,或者是搜一發提示語,其他網絡請求操作也是分散在各個控制器內,亂七八糟。好煩的。趁著有點空閑,研究了下別人的封裝,改進了項目中使用的封裝,并做成了SDK發布在cocoaPods上,歡迎使用.
pod 'TYNetworkTool'
即可使用。
項目原先的AFN封裝
也就是很簡單的按照AFN封裝了一下,.m
//
// PPRHttpManager.m
// PaopaoRunning
//
// Created by 王天永 on 16/5/22.
//
#import "PPRHttpManager.h"
#import <AFNetworking.h>
#import "NSString+Paths.h"
#import "NSString+UUID.h"
@implementation PPRHttpManager
+ (NSURLSessionDataTask *)get:(NSString *)url params:(NSDictionary *)params success:(void (^)(id responseObject))success failure:(void (^)(NSError *error))failure{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
if (IOS11) { //適配IOS11增加的這句
manager.securityPolicy.allowInvalidCertificates = YES;
}
manager.requestSerializer.timeoutInterval = 15;
// [manager.requestSerializer setValue:@"application/x-www-form-urlencoded; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
NSURLSessionDataTask *task = [manager GET:url parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (success){
NSString *responseString = [[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding];
PRLog(@"responseString---%@",responseString);
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingAllowFragments error:nil];
success(dict);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (failure){
failure(error);
}
}];
return task;
}
+ (NSURLSessionDataTask *)post:(NSString *)url params:(NSDictionary *)params success:(void (^)(id responseObject))success failure:(void (^)(NSError *error))failure{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
// manager.securityPolicy.allowInvalidCertificates = YES;
manager.requestSerializer.timeoutInterval = 15;
NSURLSessionDataTask *task = [manager POST:url parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (success){
NSString *responseString = [[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding];
PRLog(@"responseString---%@",responseString);
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingAllowFragments error:nil];
success(dict);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (failure){
failure(error);
}
}];
return task;
}
+ (NSURLSessionDownloadTask *)downloadFile:(NSString *)url progress:(void (^)(CGFloat progress))progress success:(void (^)(id responseObject))success failure:(void (^)(NSError *error))failure{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:url];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress *downloadProgress){
// NSLog(@"progress is %f", downloadProgress.fractionCompleted);
progress(downloadProgress.fractionCompleted);
} destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSString *caches = [NSString cachesPath];
NSString *videovideoCaches = [caches stringByAppendingPathComponent:@"videoCaches"];
NSURL *videoFileUrl = [NSURL fileURLWithPath:videovideoCaches];
return [videoFileUrl URLByAppendingPathComponent:[response suggestedFilename] ];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
if (!error){
PRLog(@"%@",F(@"File downloaded to: %@", filePath));
success(filePath);
}else{
PRLog(@"File download fail!");
failure(error);
}
}];
[downloadTask resume];
return downloadTask;
}
@end
著手封裝新的低耦合的AFN網絡封裝
封裝的主要方向
- 本地緩存優化,請求可根據需求(緩存的時效性等)決定是否直接使用本地緩存,或者請求到新數據后刷新本地。(當前已完成)
- 增加短時緩存,避免相同的請求重復調用,浪費用戶流量。(當前已完成)
- 封裝請求失敗的重試機制。
- 將成功失敗提示的邏輯封裝。
- 其他比如網絡狀況監控,請求一鍵撤銷等。(當前已完成)
感謝巨巨文章提供思路和參考
猿題庫YTKNetwork的GitHub
iOS 工作中封裝通用性網絡請求框架——鴻雁長飛光不度
AFNetworking3.x與YYCache的二次封裝,和FMDB說拜拜——jkpang
封裝的結構考慮
- 考慮以后換網絡框架的可能。(網絡單獨封裝
- 考慮以后換回調處理及提示的可能。網絡請求和回調處理要低耦合,能夠單獨抽離。(回調和顯示的單獨封裝
- 考慮以后換緩存框架的可能。(緩存單獨封裝
封裝的結構
考慮到網絡請求的通用性,而回調處理和提示等并不通用,所以回調的封裝暫時不納入SDK,但是仍然建議將網絡請求的回調封裝起來存于一處,相同回調調用相應的統一方法,以后需要改動改這一處就夠了,如本人GitHub上的TYNetworkManage
文件夾內所封裝的方式。
所以最終的SDK內的文件結構是這樣的(暫時
TYNetworkTool封裝結構
· 第一層 網絡請求入口
TYNetworkTool
,.h文件對外暴露,用戶之間調用即可完成網絡請求。.m調用TYCacheTool.h
的方法來完成緩存的存儲和讀取;· 第二層 緩存的存取入口
TYCacheTool
,主要來完成緩存的業務邏輯· 第三層 FMDB的實際緩存操作,實際操控SQLite文件來進行存取操作。
文件內容
//
// TYNetworkTool.h
// TYNetworkHelper
//
// Created by 王天永 on 2017/7/14.
// Copyright ? 2017年 王天永. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "TYCacheTool.h"
typedef NS_ENUM(NSUInteger, TYNetworkStatusType) {
/// 未知網絡
TYNetworkStatusUnknown,
/// 無網絡
TYNetworkStatusNotReachable,
/// 手機網絡
TYNetworkStatusReachableViaWWAN,
/// WIFI網絡
TYNetworkStatusReachableViaWiFi
};
typedef NS_ENUM(NSUInteger, TYRequestSerializer) {
/// 設置請求數據為JSON格式
TYRequestSerializerJSON,
/// 設置請求數據為二進制格式
TYRequestSerializerHTTP,
};
typedef NS_ENUM(NSUInteger, TYResponseSerializer) {
/// 設置響應數據為JSON格式
TYResponseSerializerJSON,
/// 設置響應數據為二進制格式
TYResponseSerializerHTTP,
};
/// 請求成功的Block
typedef void(^TYHttpRequestSuccess)(id responseObject);
/// 請求失敗的Block
typedef void(^TYHttpRequestFailed)(NSError *error);
/// 緩存的Block
typedef void(^TYHttpRequestCache)(id responseCache);
/// 上傳或者下載的進度, Progress.completedUnitCount:當前大小 - Progress.totalUnitCount:總大小
typedef void (^TYHttTYrogress)(NSProgress *progress);
/// 網絡狀態的Block
typedef void(^TYNetworkStatus)(TYNetworkStatusType status);
@class AFHTTPSessionManager;
@interface TYNetworkTool : NSObject
/// 有網YES, 無網:NO
+ (BOOL)isNetwork;
/// 手機網絡:YES, 反之:NO
+ (BOOL)isWWANNetwork;
/// WiFi網絡:YES, 反之:NO
+ (BOOL)isWiFiNetwork;
/// 取消所有HTTP請求
+ (void)cancelAllRequest;
/// 實時獲取網絡狀態,通過Block回調實時獲取(此方法可多次調用)
+ (void)networkStatusWithBlock:(TYNetworkStatus)networkStatus;
/// 取消指定URL的HTTP請求
+ (void)cancelRequestWithURL:(NSString *)URL;
/// 開啟日志打印 (Debug級別)
+ (void)openLog;
/// 關閉日志打印,默認關閉
+ (void)closeLog;
/**
* GET請求,無緩存
*
* @param URL 請求地址
* @param parameters 請求參數
* @param success 請求成功的回調
* @param failure 請求失敗的回調
*
* @return 返回的對象可取消請求,調用cancel方法
*/
+ (__kindof NSURLSessionTask *)GET:(NSString *)URL
parameters:(id)parameters
success:(TYHttpRequestSuccess)success
failure:(TYHttpRequestFailed)failure;
/**
* GET請求,自動緩存
*
* @param URL 請求地址
* @param parameters 請求參數
* @param responseCache 緩存數據的回調
* @param success 請求成功的回調
* @param failure 請求失敗的回調
*
* @return 返回的對象可取消請求,調用cancel方法
*/
+ (__kindof NSURLSessionTask *)GET:(NSString *)URL
parameters:(id)parameters
responseCache:(TYHttpRequestCache)responseCache
success:(TYHttpRequestSuccess)success
failure:(TYHttpRequestFailed)failure;
/**
GET請求,帶時效自動緩存
@param URL 請求地址
@param parameters 請求參數
@param life 緩存時效
@param responseCache 緩存數據的回調
@param success 請求成功的回調
@param failure 請求失敗的回調
@return 返回的對象可取消請求,調用cancel方法
*/
+ (__kindof NSURLSessionTask *)GET:(NSString *)URL
parameters:(id)parameters
userfulLifeUnit:(TYTimeUnit)timeUnit
userfullLife:(double)life
responseCache:(TYHttpRequestCache)responseCache
success:(TYHttpRequestSuccess)success
failure:(TYHttpRequestFailed)failure;
/**
* POST請求,無緩存
*
* @param URL 請求地址``
* @param parameters 請求參數
* @param success 請求成功的回調
* @param failure 請求失敗的回調
*
* @return 返回的對象可取消請求,調用cancel方法
*/
+ (__kindof NSURLSessionTask *)POST:(NSString *)URL
parameters:(id)parameters
success:(TYHttpRequestSuccess)success
failure:(TYHttpRequestFailed)failure;
/**
* POST請求,自動緩存
*
* @param URL 請求地址
* @param parameters 請求參數
* @param responseCache 緩存數據的回調
* @param success 請求成功的回調
* @param failure 請求失敗的回調
*
* @return 返回的對象可取消請求,調用cancel方法
*/
+ (__kindof NSURLSessionTask *)POST:(NSString *)URL
parameters:(id)parameters
responseCache:(TYHttpRequestCache)responseCache
success:(TYHttpRequestSuccess)success
failure:(TYHttpRequestFailed)failure;
/**
GET請求,帶時效自動緩存
@param URL 請求地址
@param parameters 請求參數
@param life 緩存時效
@param responseCache 緩存數據的回調
@param success 請求成功的回調
@param failure 請求失敗的回調
@return 返回的對象可取消請求,調用cancel方法
*/
+ (__kindof NSURLSessionTask *)POST:(NSString *)URL
parameters:(id)parameters
userfulLifeUnit:(TYTimeUnit)timeUnit
userfullLife:(double)life
responseCache:(TYHttpRequestCache)responseCache
success:(TYHttpRequestSuccess)success
failure:(TYHttpRequestFailed)failure;
/**
* 上傳文件
*
* @param URL 請求地址
* @param parameters 請求參數
* @param name 文件對應服務器上的字段
* @param filePath 文件本地的沙盒路徑
* @param progress 上傳進度信息
* @param success 請求成功的回調
* @param failure 請求失敗的回調
*
* @return 返回的對象可取消請求,調用cancel方法
*/
+ (__kindof NSURLSessionTask *)uploadFileWithURL:(NSString *)URL
parameters:(id)parameters
name:(NSString *)name
filePath:(NSString *)filePath
progress:(TYHttTYrogress)progress
success:(TYHttpRequestSuccess)success
failure:(TYHttpRequestFailed)failure;
/**
* 上傳單/多張圖片
*
* @param URL 請求地址
* @param parameters 請求參數
* @param name 圖片對應服務器上的字段
* @param images 圖片數組
* @param fileNames 圖片文件名數組, 可以為nil, 數組內的文件名默認為當前日期時間"yyyyMMddHHmmss"
* @param imageScale 圖片文件壓縮比 范圍 (0.f ~ 1.f)
* @param imageType 圖片文件的類型,例:png、jpg(默認類型)....
* @param progress 上傳進度信息
* @param success 請求成功的回調
* @param failure 請求失敗的回調
*
* @return 返回的對象可取消請求,調用cancel方法
*/
+ (__kindof NSURLSessionTask *)uploadImagesWithURL:(NSString *)URL
parameters:(id)parameters
name:(NSString *)name
images:(NSArray<UIImage *> *)images
fileNames:(NSArray<NSString *> *)fileNames
imageScale:(CGFloat)imageScale
imageType:(NSString *)imageType
progress:(TYHttTYrogress)progress
success:(TYHttpRequestSuccess)success
failure:(TYHttpRequestFailed)failure;
/**
* 下載文件
*
* @param URL 請求地址
* @param fileDir 文件存儲目錄(默認存儲目錄為Download)
* @param progress 文件下載的進度信息
* @param success 下載成功的回調(回調參數filePath:文件的路徑)
* @param failure 下載失敗的回調
*
* @return 返回NSURLSessionDownloadTask實例,可用于暫停繼續,暫停調用suspend方法,開始下載調用resume方法
*/
+ (__kindof NSURLSessionTask *)downloadWithURL:(NSString *)URL
fileDir:(NSString *)fileDir
progress:(TYHttTYrogress)progress
success:(void(^)(NSString *filePath))success
failure:(TYHttpRequestFailed)failure;
#pragma mark - 設置AFHTTPSessionManager相關屬性
#pragma mark 注意: 因為全局只有一個AFHTTPSessionManager實例,所以以下設置方式全局生效
/**
在開發中,如果以下的設置方式不滿足項目的需求,就調用此方法獲取AFHTTPSessionManager實例進行自定義設置
(注意: 調用此方法時在要導入AFNetworking.h頭文件,否則可能會報找不到AFHTTPSessionManager的?)
@param sessionManager AFHTTPSessionManager的實例
*/
+ (void)setAFHTTPSessionManagerProperty:(void(^)(AFHTTPSessionManager *sessionManager))sessionManager;
/**
* 設置網絡請求參數的格式:默認為二進制格式
*
* @param requestSerializer TYRequestSerializerJSON(JSON格式),TYRequestSerializerHTTP(二進制格式),
*/
+ (void)setRequestSerializer:(TYRequestSerializer)requestSerializer;
/**
* 設置服務器響應數據格式:默認為JSON格式
*
* @param responseSerializer TYResponseSerializerJSON(JSON格式),TYResponseSerializerHTTP(二進制格式)
*/
+ (void)setResponseSerializer:(TYResponseSerializer)responseSerializer;
/**
* 設置請求超時時間:默認為30S
*
* @param time 時長
*/
+ (void)setRequestTimeoutInterval:(NSTimeInterval)time;
/// 設置請求頭
+ (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;
/**
* 是否打開網絡狀態轉圈菊花:默認打開
*
* @param open YES(打開), NO(關閉)
*/
+ (void)openNetworkActivityIndicator:(BOOL)open;
/**
配置自建證書的Https請求, 參考鏈接: http://blog.csdn.net/syg90178aw/article/details/52839103
@param cerPath 自建Https證書的路徑
@param validatesDomainName 是否需要驗證域名,默認為YES. 如果證書的域名與請求的域名不一致,需設置為NO; 即服務器使用其他可信任機構頒發
的證書,也可以建立連接,這個非常危險, 建議打開.validatesDomainName=NO, 主要用于這種情況:客戶端請求的是子域名, 而證書上的是另外
一個域名。因為SSL證書上的域名是獨立的,假如證書上注冊的域名是www.google.com, 那么mail.google.com是無法驗證通過的.
*/
+ (void)setSecurityPolicyWithCerPath:(NSString *)cerPath validatesDomainName:(BOOL)validatesDomainName;
+ (NSString *)jsonToString:(id)data;
@end
//
// TYCacheTool.h
// TYNetworkTool
//
// Created by 王天永 on 2017/7/19.
// Copyright ? 2017年 王天永. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NS_ENUM (NSUInteger,TYTimeUnit) {
TYNone,
TYSecond,
TYMinute,
TYHour,
TYDay,
};
@interface TYCacheTool : NSObject
/**
無失效日期緩存
@param httpData 緩存數據
@param url 請求url
@param parameters 請求參數
*/
+ (void)setHttpCache:(id)httpData URL:(NSString *)url parameters:(NSDictionary *)parameters;
/**
帶時效的緩存
@param httpData 緩存數據
@param url 請求url
@param parameters 請求參數
@param timeUnit 時間單位,TYNone則無時效
@param life 時效數值,具體單位與timeUnit有關,最小單位 秒
*/
+ (void)setHttpCache:(id)httpData URL:(NSString *)url parameters:(NSDictionary *)parameters userfulLifeUnit:(TYTimeUnit)timeUnit life:(double)life;
/**
獲取緩存數據
@param url 請求url
@param parameter 請求參數
@return 緩存數據
*/
+ (id)httpCacheWithURL:(NSString *)url parameters:(NSDictionary *)parameter;
@end
//
// TYFMDBTool.h
// TYNetworkTool
//
// Created by 王天永 on 2017/7/20.
// Copyright ? 2017年 王天永. All rights reserved.
//
#import <Foundation/Foundation.h>
@class TYCacheTool;
@interface TYFMDBTool : NSObject
/**
創建tool實例,包含一個dmdb實例對象
@param name 對應賬號的唯一標識符
@return 返回fmdbtool實例
*/
+ (instancetype)fmdbWithName:(NSString *)name;
/**
創建表單
@param tableName 表單名
@return FMDB實例
*/
- (instancetype)createTableWithName:(NSString *)tableName;
/**
儲存緩存請求來的回調數據(無時效)
@param httpData 回調數據
@param cacheKey 存儲的關鍵字
*/
- (void)setHttpCache:(id)httpData forCacheKey:(NSString *)cacheKey;
/**
帶時效的緩存
@param httpData 緩存數據
@param cacheKey 存儲的關鍵字
@param life 時效數值,具體單位與timeUnit有關
*/
- (void)setHttpCache:(id)httpData userfulLife:(double)life forCacheKey:(NSString *)cacheKey;
/**
獲取本地緩存
@param cacheKey 存儲的關鍵字
@return 緩存本地的網絡請求
*/
- (id)httpCacheForCacheKey:(NSString *)cacheKey;
@end
感覺帶時效的緩存還有改進的余地,后續再繼續改進吧,以新增方法的方式。(當前的方式適合這樣的需求:如果是有效的緩存,先讀取了顯示在界面上,等數據獲取到了再顯示獲取到的數據。但是這樣就沒有達到節省流量的目的了,需要再SDK內增加先取緩存,無有效緩存再發送網絡請求的方法。
本人的這個SDK已經發布在cocoapods上,pod 'TYNetworkTool'
即可使用。