前言:目前的開發中,處理網絡請求的時候其實很多重復的請求,例如請求廣告詞語之類,每次打開app都會重復上一次的請求。
基于這個重復的考慮,想要優化我們就要把已經請求過的響應體做成持久化的存儲,下一次再有重復的請求就可以直接從緩沖里面取出而不必要再次進行網絡請求。
根據我們的設想,可分如下步驟:
已經成功請求的響應,進行持久化存儲。
設置持久化存儲的緩沖有效期
-
再次進行相應的請求,可以根據不同情況是否再次發送網絡請求,如下:
a.沒有持久化緩沖 如果沒有緩沖過的,只能發起新的網絡請求,然后進行持久化緩存 b.已經持久化緩沖,緩沖已經過期 已經過期的緩存,也只能發起新的網絡請求,如果網絡請求失敗,只能把過期的緩存返回 c.已經持久化緩沖,緩沖還在有效期內,但是要求強行更新請求的設置 即時已經有還在有效期內的緩存,但是如果發起強行更新的命令,也要從新發送新的網絡請求,同時也要更新數據庫的緩存和時間有限期 d.已經持久化緩沖,緩沖還在有效期內,并且沒有強行更新請求的設置 已經有還在有效期內的緩存,并且沒有強行更新請求,那么不必要在發送新的網絡請求,直接從數據庫緩存中返回響應體
HttpCachesLoader主要邏輯代碼如下:
頭文件
@class FMDatabaseQueue;
#import <Foundation/Foundation.h>
//定義緩沖有效期
#define MINUTE 60
#define HOUR 60*60
#define DAY HOUR*24
#define MONTH DAY*30
#define YEAR MONTH*12
//定義請求方式
typedef NS_ENUM(NSInteger , HttpRequestMethod) {
HttpRequestMethodGet ,
HttpRequestMethodPost
};
@interface HttpCachesLoader : NSObject
/** 緩沖有效期 單位:秒 默認1min*/
@property (nonatomic,assign)double timeLimit;
/**
* 默認的類方法,會在沙河的caches路徑創建一個數據庫,并且建立一個存放緩沖的表HttpRequestCaches
*
* @return 返回一個HttpCachesLoader對象
*/
+(instancetype)loaderDefault;
+(instancetype)loaderWithPath:(NSString *)path;
+(instancetype)loaderWithFMDatabaseQueue:(FMDatabaseQueue *)queue;
-(instancetype)initWithPath:(NSString *)path;
-(instancetype)initWithFMDatabaseQueue:(FMDatabaseQueue *)queue;
/**
* 通過GET 或者 POST請求方式
*
* @param url 請求地址
* @param dict 請求參數
* @param immediately 是否強制更新
* @param block 請求完成后的回調,id類型存放請求返回的數據
*/
-(void)GET:(NSString *)url parameters:(NSDictionary *)dict completion:(void(^)(id))block;
-(void)GET:(NSString *)url parameters:(NSDictionary *)dict immediately:(BOOL)immediately completion:(void(^)(id))block;
-(void)POST:(NSString *)url parameters:(NSDictionary *)dict completion:(void(^)(id))block;
-(void)POST:(NSString *)url parameters:(NSDictionary *)dict immediately:(BOOL)immediately completion:(void(^)(id))block;
@end
實現文件,這里主要說實現思路(使用到FMDB 和 AFNetworking框架)
1.如果通過默認的類方法創建HttpCachesLoader對象,那么在默認的沙河路徑caches文件夾里面會創建一個數據庫文件,并且建立一個存放緩沖的表HttpRequestCaches
HttpCachesLoader *loader = [HttpCachesLoader loaderDefault];
+(instancetype)loaderDefault{
HttpCachesLoader *loader = [[self alloc]init];
return loader;
}
-(instancetype)init{
if (self = [super init]) {
NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"defaultHttpCaches.db"];
NSLog(@"---默認數據庫存放地址---:%@",path);
_queue = [FMDatabaseQueue databaseQueueWithPath:path];
[self setup];
}
return self;
}
2.使用相應的對象方法發送請求
-(void)GET:(NSString *)url parameters:(NSDictionary *)dict immediately:(BOOL)immediately completion:(void(^)(id))block;
#舉例說明該對象方法的實現思路
//拼接url和參數 主要用來查詢數據庫的key
NSString *url_parameters = [NSString stringWithFormat:@"%@?%@",url,[self sortParameter:dict]];
//根據url_parameters查找是否已經緩沖數據
NSString *sqlSelect = [NSString stringWithFormat:@"select * from HttpRequestCaches where request = '%@'",url_parameters];
//是否緩沖的標志
__block BOOL isCaches = NO;
//緩沖是否過期的標志
__block BOOL isCachesTimeOut = NO;
//重開數據庫連接
[self.queue restartDataBase];
//先到數據查詢是否有相應的記錄
[self.queue inDatabase:^(FMDatabase *db) {
FMResultSet *result = [db executeQuery:sqlSelect];
if ((isCaches =[result next])) {
NSTimeInterval cachesInterval = [[NSDate localDate] timeIntervalSinceDate:[NSDate dateFromString:[result stringForColumn:@"time"]]];
//計算緩沖是否已經過期
isCachesTimeOut = cachesInterval > self.timeLimit;
//緩沖還沒過期,先把結果從數據庫取出
if (!isCachesTimeOut) dataResult = [result dataForColumn:@"response"];
[result close];
}
}];
//數據操作
NSString *sqlUpdateOrInsert;
if (!isCaches) {
//如果沒有緩沖過,執行插入輸入庫操作
sqlUpdateOrInsert = @"insert into HttpRequestCaches (time,response,url,request) values (?,?,?,?)";
NSLog(@"沒有緩沖過,執行插入輸入庫操作");
}else{
//如果緩沖沒有超時,而且請求為immediately=NO的話,證明數據庫中的緩沖還符合要求,取出結果返回。結束請求
if (!immediately && !isCachesTimeOut) {
if (block) {
NSLog(@"緩沖沒有超時,而且請求為immediately=NO的話,證明數據庫中的緩沖還符合要求,取出結果返回。結束請求");
block(dataResult);
}
return;
}
//如果緩沖過但是已經超時,或者請求為immediately=YES的話,執行跟新數據庫操作
sqlUpdateOrInsert = @"update HttpRequestCaches set time = ? ,response = ? ,url = ? where request = ?";
NSLog(@"緩沖過但是已經超時,或者請求為immediately=YES的話,執行跟新數據庫操作");
}
if (method == HttpRequestMethodGet) {//GET請求
[self.mgr GET:url parameters:dict progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"---請求成功---%@",task.currentRequest.URL);
NSData *data = [NSJSONSerialization dataWithJSONObject:responseObject options:NSJSONWritingPrettyPrinted error:nil];
[self.queue inDatabase:^(FMDatabase *db) {
[db executeUpdate:sqlUpdateOrInsert,[NSDate stringFromLocalDate],data,url,url_parameters];
}];
dataResult = data;
//把最終結果通過block傳遞出去
if (block) {
block(dataResult);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"---請求失敗---%@",error);
[self.queue inDatabase:^(FMDatabase *db) {
FMResultSet *timeoutResult = [db executeQuery:sqlSelect];
if ([timeoutResult next]) {
dataResult = [timeoutResult dataForColumn:@"response"];
[timeoutResult close];
}
}];
//把最終結果通過block傳遞出去
if (block) {
block(dataResult);
}
}];
}else if(method == HttpRequestMethodPost){//POST請求
[self.mgr POST:url parameters:dict progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"---請求成功---%@",task.currentRequest.URL);
NSData *data = [NSJSONSerialization dataWithJSONObject:responseObject options:NSJSONWritingPrettyPrinted error:nil];
[self.queue inDatabase:^(FMDatabase *db) {
[db executeUpdate:sqlUpdateOrInsert,[NSDate stringFromLocalDate],data,url,url_parameters];
}];
dataResult = data;
//把最終結果通過block傳遞出去
if (block) {
block(dataResult);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"---請求失敗---%@",error);
[self.queue inDatabase:^(FMDatabase *db) {
FMResultSet *timeoutResult = [db executeQuery:sqlSelect];
if ([timeoutResult next]) {
dataResult = [timeoutResult dataForColumn:@"response"];
[timeoutResult close];
}
//把最終結果通過block傳遞出去
if (block) {
block(dataResult);
}
}];
}];
}
項目git地址:https://github.com/kinglchristina/HttpCachesLoader.git