在iOS開發(fā)中,網(wǎng)絡(luò)是必不可少的一部分,沒有人不知道大名鼎鼎的AFNetwork框架的,因?yàn)樗峁┝朔浅XS富實(shí)用,方便的網(wǎng)絡(luò)調(diào)用。使得很多需求都能夠調(diào)用已有的方法完成。但是面對(duì)業(yè)務(wù)需求,如何合理的將AFNetwork近一步封裝能夠更加方便的完成業(yè)務(wù)需求卻是需要好好考慮的。以下根據(jù)自己的工作經(jīng)歷中對(duì)AFNetwork的封裝。
一、首次接觸
自己在A公司的時(shí)候剛剛接觸iOS不久,看到的工程中已經(jīng)存在的封裝是這樣子的
+ (void)postJSONWithUrl:(NSString *)urlStr parameters:(NSMutableDictionary *)dic success:(void (^)(id responseObject))success fail:(void (^)())fail;
+ (void)JSONDataWithUrl:(NSString *)url andDic:(NSMutableDictionary *)dic success:(void (^)(id json))success fail:(void (^)(id error))fail;
這兩個(gè)函數(shù)很簡(jiǎn)單,就是發(fā)起一個(gè)網(wǎng)絡(luò)請(qǐng)求,使用的時(shí)候urlStr基本上就是固定的用宏來定義的。dic就是傳遞的字典.success 和fail 分別表示請(qǐng)求成功和失敗的回調(diào)。但是他們?nèi)秉c(diǎn)重重,最明顯的
- post和get應(yīng)該可以用一個(gè)方法來實(shí)現(xiàn)的。
- urlStr每次都寫都要寫太麻煩。
二、接觸正規(guī)需求
后來就是現(xiàn)在的公司了,因?yàn)闃I(yè)務(wù)需求比較正規(guī)了,iOS客戶端又是剛剛開始研發(fā),所以跟我提了一系列需求,讓我封裝一套通用網(wǎng)絡(luò)方法,相信這也是大部分互聯(lián)網(wǎng)公司的需求吧,只是我以前見識(shí)太少。
- 網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)要能夠緩存,但是也可以不進(jìn)行緩存,緩存的數(shù)據(jù)還要可以設(shè)置緩存時(shí)間。
- 對(duì)于每次網(wǎng)絡(luò)請(qǐng)求要優(yōu)先從緩存中讀取內(nèi)容,如果緩存讀取成功,就用回調(diào)成功block,然后檢查緩存是否過期,過期就從網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù),然后把新的數(shù)據(jù)存入到緩存中。
- 網(wǎng)絡(luò)請(qǐng)求的結(jié)果:
- 成功獲得數(shù)據(jù)
- 請(qǐng)求數(shù)據(jù)失敗、返回特殊狀態(tài)碼。(用戶沒有登陸,已經(jīng)是最新數(shù)據(jù)等等)
緩存的應(yīng)用場(chǎng)景主要是tableview加載數(shù)據(jù),其他的場(chǎng)景也適合,以下以tableview為例
-
首次
進(jìn)入一個(gè)界面tableview的數(shù)據(jù)應(yīng)該先讀取緩存,主要防止沒有網(wǎng)絡(luò)或者網(wǎng)絡(luò)不好的情況下用戶等待數(shù)據(jù)時(shí)間過長(zhǎng)或者沒有數(shù)據(jù)用戶體驗(yàn)不好,主要用于某些tableview請(qǐng)求的數(shù)據(jù)量很大
,或者數(shù)據(jù)短時(shí)間內(nèi)變化不是很快
的地方。 - 用戶下拉刷新表示重新請(qǐng)求數(shù)據(jù),應(yīng)該強(qiáng)制更新數(shù)據(jù),不論本地是否有緩存,但是這部分?jǐn)?shù)據(jù)應(yīng)該存入數(shù)據(jù)庫(kù),保證退出后下次進(jìn)入界面的時(shí)候能夠讀取最后更新的緩存。
- 上拉加載更多的時(shí)候不讀緩存,因?yàn)槿绻蛻舳俗约涸谔幚磉@部分邏輯比較復(fù)雜,不是說實(shí)現(xiàn)起來復(fù)雜,因?yàn)閷?duì)于本地存儲(chǔ)來說調(diào)用的是統(tǒng)一的一個(gè)方法,主要是因?yàn)槿绻脩粝吕⑿滦枰獜?qiáng)制更新數(shù)據(jù),不論本地有無緩存,都要從服務(wù)器請(qǐng)求最新數(shù)據(jù)。那么問題來了,如果我把用戶上拉加載更多時(shí)候的數(shù)據(jù)也存入本地,假設(shè)用戶上拉加載了5頁數(shù)據(jù),然后又
強(qiáng)制下拉
刷新了一下,這個(gè)時(shí)候tableview顯示的是最新的第一頁數(shù)據(jù),如果接著上拉加載更多
我應(yīng)該是讀取緩存還是直接發(fā)起網(wǎng)絡(luò)請(qǐng)求呢。毫無疑問應(yīng)該直接發(fā)起請(qǐng)求,因?yàn)橄吕瓘?qiáng)制刷新已經(jīng)導(dǎo)致了第一頁為最新數(shù)據(jù),如果第2-5頁數(shù)據(jù)緩存沒有過期并且服務(wù)器數(shù)據(jù)確實(shí)變化了的話,客戶端將得不到新的數(shù)據(jù),所以僅僅第一次進(jìn)入tableview界面,請(qǐng)求第一頁數(shù)據(jù)的時(shí)候要讀緩存。 - 根本不用緩存的地方主要是某些數(shù)據(jù)變化非常快,或者發(fā)起的網(wǎng)絡(luò)請(qǐng)求是一次性的操作,比如收藏某個(gè)題目等等。
根據(jù)上面的需求,我首先想到用sqlite作為緩存的數(shù)據(jù)庫(kù),面對(duì)一個(gè)完整的url和url請(qǐng)求得到的json字符串提供增刪改查,用了一天時(shí)間我封裝了屬于工程的數(shù)據(jù)庫(kù)。提供了如下接口
/**
* 插入一個(gè)json緩存數(shù)據(jù)
* @param key 鍵
* @param value 數(shù)據(jù)
* @param expire_time 過期時(shí)間
*/
+ (void)insertJonsCacheStringWithKey:(NSString *)key andWithValue:(NSString*) value andWithExpireTime:(NSInteger )expire_time;
/**
* 根據(jù)JSON緩存獲取一個(gè)包含對(duì)JSON數(shù)據(jù)操作的對(duì)象
* @param key 鍵
* @return --- JsonCacheData類型的可以對(duì)提取的json處理
*/
+(JsonCacheData *)queryJsonCacheTableWithKey:(NSString *)key;
/**
* 插入的值是字典類型
* @param key 鍵
* @param value 值
* @param expire_time 過期時(shí)間
*/
+ (void)insertJsonCacheDictWithKey:(NSString *)key andWithValue:(NSDictionary*) value andWithExpireTime:(NSInteger )expire_time;
/**
* 刪除數(shù)據(jù)緩存
* @param key
*/
+ (BOOL)deleteJsonCacheDictWithKey:(NSString *)key;
并且創(chuàng)建了一個(gè)JsonCacheData對(duì)象存儲(chǔ)數(shù)據(jù)庫(kù)的數(shù)據(jù),因?yàn)橛械臅r(shí)候存入數(shù)據(jù)庫(kù)的不一定是json,很可能是個(gè)字符串什么,這樣可以按照不同的需求調(diào)用類里面的方法得到自己想要的數(shù)據(jù)類型.
@interface JsonCacheData : NSObject
//數(shù)據(jù)存入數(shù)據(jù)庫(kù)的時(shí)間戳
@property (nonatomic,assign) NSInteger saveTime;
//緩存有效期長(zhǎng)度
@property (nonatomic,assign) NSInteger expireTime;
//從數(shù)據(jù)庫(kù)返回的字符串信息
@property (nonatomic,strong) NSString * jsonCache;
-(BOOL)isExpire;
/**
* 轉(zhuǎn)換成JSON類型
* @return JSON數(shù)據(jù)的字典
*/
-(NSDictionary *)JsonData;
@end
AFNet封裝完成后函數(shù)如下
/**
* 網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)的方法,可以進(jìn)行緩存
* @param param 上傳請(qǐng)求的參數(shù)
* @param success 獲取的json數(shù)據(jù),可能是網(wǎng)絡(luò)請(qǐng)求的,也可能是本地緩存的
* @param codeError 網(wǎng)絡(luò)返回?cái)?shù)據(jù)的錯(cuò)誤碼
* @param fail 網(wǎng)絡(luò)連接失敗
* @param minutes 緩存的有效時(shí)長(zhǎng)
*/
+(void)loadDataWithCache:(NSDictionary *) param NetBlock:(void (^)(NSDictionary *json))success ErrorCode :(void(^)(int errorCode))codeError Fail:(void(^)())fail cacheTime:(int) minutes
{
NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithDictionary:param];
NSString * sqliteKey = [HSNetUrlProcess createSqliteKeyWithParm:dict];
JsonCacheData * data = [SQLiteJsonCache queryJsonCacheTableWithKey:sqliteKey];
//緩存存在并且,緩存時(shí)間大于0表示要求讀取緩存的
if (data.jsonCache.length>0&&minutes>0)
{
if (data.jsonCache.length>0) { //如果緩存存在, 有緩存就顯示,并且緩存時(shí)間要大于0否則就沒有意義
if (success!=nil) {
success(data.JsonData);
}
if (data.isExpire) {
if ([data.JsonData[@"version"]length]>0) {
dict[@"version"]=data.JsonData[@"version"];
}
//這里調(diào)用網(wǎng)絡(luò)方法請(qǐng)求數(shù)據(jù)
[AFNetworkTool JSONDataWithDic:dict success:^(id json) {
if (json!=nil) {
//加密
[SQLiteJsonCache insertJsonCacheDictWithKey:sqliteKey andWithValue:json andWithExpireTime:60*minutes];
success(json);
}
} codeError:^(int errorCode) {
if (codeError!=nil) {
if (errorCode==NetCode_RESULT_NEWEST) { //更新緩存的時(shí)間,已經(jīng)是最新數(shù)據(jù)
[SQLiteJsonCache insertJsonCacheDictWithKey:sqliteKey andWithValue:data.JsonData andWithExpireTime:minutes*60];
}
codeError(errorCode);
}
} fail:^{
if (fail!=nil) {
fail();
}
}];
}
}
}
else //這里是不讀取緩存
{
[AFNetworkTool JSONDataWithDic:dict success:^(id json) {
if (minutes!=CACHE_NO_SAVE) { // CACHE_NO_SAVE = -1 表示不需要存入數(shù)據(jù)庫(kù)。
[SQLiteJsonCache insertJsonCacheDictWithKey:sqliteKey andWithValue:json andWithExpireTime:minutes*60];
}
success(json);
} codeError:^(int errorCode) {
if (codeError!=nil) {
codeError(errorCode);
}
} fail:^{
if (fail!=nil) {
fail();
}
}];
}
}
傳遞的cachetime有以下類型
CACHE_NOSAVE | CACHE_NOREAD | CACHE_READ_NORMAL |
---|---|---|
-1 | 0 | 10 |
不讀緩存 | 不讀取緩存,但是要將獲取的數(shù)據(jù)按照CACHE_READ_NO有效期存儲(chǔ) | 大部分情況下緩存有效期 |
上面的封裝也不太好,但是時(shí)間太緊迫,趕鴨子上架,而且都等著用我寫的網(wǎng)絡(luò)方法呢,所有就先這樣了,在使用的時(shí)候暴露了很多問題:
- 網(wǎng)絡(luò)請(qǐng)求有的時(shí)候需要控制SVProgressHud的顯示,但是很多場(chǎng)合用,要么多了,要么少了,每次網(wǎng)絡(luò)調(diào)用需要自己加麻煩。
- 真正使用起來雞肋,為了完成上述tableview請(qǐng)求數(shù)據(jù)的需求。我們只能這樣做
self.cacheTime = CACHE_NO_NORMAL;
[AFNetworkTool loadDataWithCache:dict NetBlock:^(NSDictionary *json) {
self.cacheTime = CACHE_NO_READ;
//process data
} ErrorCode:^(int errorCode) {
} Fail:^{
} cacheTime:self.cacheTime];
用一個(gè)全局變量保存緩存策略然后請(qǐng)求數(shù)據(jù)后在改,一個(gè)界面如果存在兩個(gè)同的網(wǎng)絡(luò)請(qǐng)求都用到緩存,就要定義兩個(gè)cachetime.太麻煩了,不能忍。
解決方案
- 我想我可以通過給網(wǎng)絡(luò)調(diào)用添加參數(shù)的形式方式擴(kuò)展功能,但是放棄了,因?yàn)槿绻o原來的方法添加一個(gè)參數(shù),工程所有的調(diào)用的地方都要改,實(shí)在麻煩,如果原來的方法保留,復(fù)制一下加個(gè)參數(shù)工程中會(huì)多出很多冗余代碼,而且參數(shù)過多調(diào)用起來也不方便。
- 自定義一個(gè)網(wǎng)絡(luò)配置類 NetSetting,包含發(fā)起一次網(wǎng)絡(luò)請(qǐng)求所有的控制策略,將類的對(duì)象作為網(wǎng)絡(luò)調(diào)用的參數(shù),網(wǎng)絡(luò)如何發(fā)起完全根據(jù)傳遞的NetSetting對(duì)象的設(shè)置
所以最終經(jīng)過我的不斷修改一個(gè)能夠適用于所有需求的網(wǎng)絡(luò)請(qǐng)求成功了。
NetSetting類包含了所有的設(shè)置,包括請(qǐng)求方式
,緩存策略
,是否加密
,是否自己?jiǎn)为?dú)控制SVProgressHud的顯示
。
typedef NS_ENUM(int, HSCacheTime) {
HSCacheNoRead = 0,
HSCacheNoSave = -1,
HSCacheNormal = 5,
HSCacheOneMinute = 1,
HSCacheOneDay = 24*60,
};
typedef enum
{
NetMethodPOST = 0,
NetMethodGET = 1,
}NetMethod;
@interface HSNetSetting : NSObject
//加載動(dòng)畫控制方式,yes表示由調(diào)用的控制器控制,NO表示有AFNetWork類控制
@property (nonatomic,assign) BOOL isCtrlHub;
//緩存的策略
@property (nonatomic,assign) HSCacheTime cachePolicy;
//是否加密
@property (nonatomic,assign) BOOL isEncrypt;
//獲取數(shù)據(jù)的方式,get請(qǐng)求或者post請(qǐng)求
@property (nonatomic,assign) NetMethod askMethod;
//只讀緩存的內(nèi)容
@property (nonatomic,assign) BOOL justReadCache;//啟動(dòng)頁廣告會(huì)用到
//默認(rèn)的設(shè)置
+(instancetype)noSaveCacheSet;//不寫緩存
+(instancetype)noReadCacheSet;//不讀緩存
+(instancetype)readCacheSet;//讀緩存
+(instancetype)noEncryptPost;
此時(shí)AFNetwork封裝的方法包含了兩個(gè)部分
- 對(duì)外開放的部分,所有的網(wǎng)絡(luò)請(qǐng)求只能調(diào)用這個(gè)方法發(fā)起。改方法會(huì)根據(jù)netSetting的配置去合理的處理網(wǎng)絡(luò)請(qǐng)求的細(xì)節(jié)。
//這是對(duì)外提供的方法
+(void)HVDataCache:(NSDictionary *) param NetBlock:(void (^)(NSDictionary *json))success
ErrorCode :(void(^)(int errorCode))codeError
Fail:(void(^)())fail
Setting:(HSNetSetting*)netSettting
{
HSNetSetting * set = netSettting ;
if (set == nil ) {
set = [HSNetSetting noSaveCacheSet];//默認(rèn)
set.isEncrypt = YES;
}
NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithDictionary:param];
NSString * sqliteKey = [HSNetUrlProcess createSqliteKeyWithParm:dict];
JsonCacheData * data = [SQLiteJsonCache queryJsonCacheTableWithKey:sqliteKey];
//讀緩存,后面的null是服務(wù)器最近返回的錯(cuò)誤數(shù)據(jù),
if (data.jsonCache.length>0&&(set.cachePolicy>HSCacheNoRead||set.justReadCache==YES))
{
if (data.jsonCache.length>0) { //如果緩存存在, 有緩存就顯示,并且緩存時(shí)間要大于0否則就沒有意義
if (success!=nil) {
if (data.isExpire==NO) {
set.cachePolicy = HSCacheNoRead;
}
success(data.JsonData);
}
if (data.isExpire) { //要把version帶上,服務(wù)器用于md5計(jì)算,是否返回?cái)?shù)據(jù)
if ([data.JsonData[@"version"]length]>0) {
dict[@"version"]=data.JsonData[@"version"];
}
int cache_span = [data.JsonData[@"cache_span"]intValue];
cache_span = cache_span>24*60?60:cache_span;
set.cachePolicy = cache_span;
[AFNetworkTool HVJSONDataWithDic:dict success:^(id json) {
//加密
[SQLiteJsonCache insertJsonCacheDictWithKey:sqliteKey andWithValue:json andWithExpireTime:set.cachePolicy*60];
set.cachePolicy = HSCacheNoRead;
if (set.justReadCache==NO) {
success(json);
}
} codeError:^(int errorCode) {
if (codeError!=nil) {
if (errorCode==NetCode_RESULT_NEWEST) { //更新緩存的時(shí)間
[SQLiteJsonCache insertJsonCacheDictWithKey:sqliteKey andWithValue:data.JsonData andWithExpireTime:cache_span *60];
}
codeError(errorCode);
}
} fail:^{
if (fail!=nil) {
fail();
}
} Setting:set];
}
}
}
else
{
if (!set.isCtrlHub) {
[SVProgressHUD show];
}
[AFNetworkTool HVJSONDataWithDic:dict success:^(id json) {
if (!set.isCtrlHub) {
[SVProgressHUD dismiss];
}
set.cachePolicy = [json[@"cache_span"]intValue];
if (set.cachePolicy!=HSCacheNoSave) {//判斷是不是 不存緩存
set.cachePolicy = HSCacheNoRead;
[SQLiteJsonCache insertJsonCacheDictWithKey:sqliteKey andWithValue:json andWithExpireTime:set.cachePolicy*60];
}
if(set.justReadCache==NO)
{
success(json);
}
else
{
codeError(NetCode_NO_OLDDATA);//
}
} codeError:^(int errorCode) {
if (codeError!=nil) {
codeError(errorCode);
}
if (!set.isCtrlHub) {
[SVProgressHUD dismiss];
}
} fail:^{
[SVProgressHUD dismiss];
if (fail!=nil) {
fail();
}
} Setting:set];
}
}
非對(duì)外開放的部分,該方法用于對(duì)外開放的方法的使用。
+ (void)HVJSONDataWithDic:(NSDictionary *)paramDict success:(void (^)(id json))success codeError:(void(^)(int errorCode))codeError fail:(void (^)())fail Setting:(HSNetSetting*)netSettting
{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer.timeoutInterval = 10.f;//設(shè)置請(qǐng)求超時(shí)的時(shí)間
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",@"image/png",@"image/jpg", nil];
//[manager.requestSerializer setValue:@"headers" forHTTPHeaderField:@"Referer: http://www.vyanke.com\n"];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithDictionary:paramDict];
NSString * url;
NSDictionary * param;
//拼接將要訪問的URL地址
NSDictionary * dict2 = [HSNetUrlProcess createCompDictWithParm:dict];
NSString * value = [dict2 mj_JSONString];
url = [HSNetUrlProcess createHeadURL:dict];
NSLog(@"URL是%@",url);
if (netSettting.isEncrypt==YES) {
param = @{@"param":[EncryProcess textEncrypt:value]};
NSLog(@"加密后完整參數(shù)%@",[EncryProcess textEncrypt:value]);
}
else
{
param = dict2;
}
if (netSettting.askMethod == NetMethodPOST) {
[manager POST:url parameters:param progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
//解密處理
NSString * str = [[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding];
if (netSettting.isEncrypt) {
str = [EncryProcess textDecrypt:str];
NSLog(@"解密后的參數(shù)=%@",str);
}
NSData *jsonData = [str dataUsingEncoding:NSUTF8StringEncoding];
NSError *err;
NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers
error:&err];
int code = [JSON[@"state"]intValue];
if (success&&code==NetCode_NETWORK_SUCCESS) {
success(JSON);
return;
}
switch (code) {
case NetCode_TOKEN_EXPIRE_ERROR://{
{
[FLLoginDataModel currentUser].isLogin = NO;
[HSCoverView showMessage:JSON[@"msg"] finishBlock:nil];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];//消失
HSLoginController * log = [[HSLoginController alloc]init];
log.hidesBottomBarWhenPushed =YES;
log.finishLoginBlock = ^{
[AFNetworkTool HVJSONDataWithDic:paramDict success:success codeError:codeError fail:fail Setting:netSettting];
};
if (![[HSTool getCurrentVC] isKindOfClass:[HSLoginController class]]) {
[[HSTool getCurrentVC].navigationController pushViewController:log animated:YES];
}
});
}
default:
[HSCoverView showMessage:JSON[@"msg"] finishBlock:nil];
}
if (code!= NetCode_NETWORK_SUCCESS&&codeError!=nil) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
codeError(code);//這里必須寫,否則tableView在使用本類的時(shí)候無法停止刷新。一直處于刷新狀態(tài).需要在調(diào)用者里面繼續(xù)調(diào)用。
});
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSString * tips = [NSString stringWithFormat:@"%ld %@",(long)error.code,error.userInfo[@"NSLocalizedDescription"]];
NSData * data = error.userInfo[@"com.alamofire.serialization.response.error.data"];
NSString * message = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"服務(wù)器的錯(cuò)誤原因:%@",message);
[HSCoverView showMessage:tips finishBlock:nil];
if (fail) {
fail(error);
}
}];
}
else if(netSettting.askMethod ==NetMethodGET){
//調(diào)用manage的 GET方法同上
}
}
公開方法
在需要向網(wǎng)絡(luò)獲取新數(shù)據(jù)的時(shí)候會(huì)把自己的所有參數(shù)傳遞給私有方法
,私有方法
在完成網(wǎng)絡(luò)請(qǐng)求的時(shí)候會(huì)把結(jié)果回調(diào)給公開方法
,然后公開方法最終把數(shù)據(jù)返回給調(diào)用者。隨著業(yè)務(wù)的需求邏輯會(huì)愈加復(fù)雜,變動(dòng)更加多樣,需要自己編寫代碼的執(zhí)行邏輯。但是這樣的方法使得在很多情況下僅僅更改某幾行代碼就實(shí)現(xiàn)了功能。
應(yīng)用場(chǎng)景
- 為了方便調(diào)用,所有的控制繼承根控制器,根控制器設(shè)置了一個(gè)HSNetSetting 屬性。并且懶加載的形式重寫了get方法
-(HSNetSetting*)defaultSet
{
if (_defaultSet == nil) {
_defaultSet =[ [HSNetSetting alloc]init];
//設(shè)置默認(rèn)的配置
}
return _defaultSet;
}
這樣所有的子類,在有tableview的時(shí)候發(fā)起網(wǎng)絡(luò)請(qǐng)求的時(shí)候直接傳遞self.defaultSet 就可以了,而且在 公開方法
內(nèi)部在完成后數(shù)據(jù)請(qǐng)求后,把傳遞的netSetting改了,如果傳遞的緩存策略是讀緩存,請(qǐng)求完成后直接改成不讀緩存,不用自己手動(dòng)更改。
- 前段時(shí)間啟動(dòng)頁加廣告,返回的是json,包含了廣告的停留時(shí)間和圖片地址,顯示與否信息。但是啟動(dòng)頁時(shí)間太短,本次廣告應(yīng)該讀取上一次的,直接在HSNetSetting中加入了
@property (nonatomic,assign) BOOL justReadCache;//啟動(dòng)頁廣告會(huì)用到
然后自己調(diào)整緩存方法的邏輯,用同一個(gè)方法實(shí)現(xiàn)需求。
- app 不是強(qiáng)制要求用戶登錄的,但是進(jìn)行某些功能要登錄。比如:在A界面點(diǎn)擊按鈕Push到B界面,但是B界面需要用戶登錄后才能獲得正常信息。否則服務(wù)器返回消息提示用戶登錄,所以會(huì)在B界面push到登錄頁,登錄完成后pop到B界面,然而剛才B頁面沒有數(shù)據(jù),如果不是tableview或者沒有下拉加載功能,只能返回A,然后再push到B界面。用戶體驗(yàn)很不好,在網(wǎng)絡(luò)封裝方法中提示用戶登錄時(shí)加入下面代碼:
HSLoginController * log = [[HSLoginController alloc]init];
log.finishLoginBlock = ^{
[AFNetworkTool HVJSONDataWithDic:paramDict success:success codeError:codeError fail:fail Setting:netSettting];
};
if (![[HSTool getCurrentVC] isKindOfClass:[HSLoginController class]]) {
[[HSTool getCurrentVC].navigationController pushViewController:log animated:YES];
}
給登錄控制器加一個(gè)block,登錄完成后重新調(diào)用一次自己剛剛的操作就能解決這問題。
SVProgressHud 網(wǎng)絡(luò)請(qǐng)求的時(shí)候需要自己總是寫麻煩,可以根據(jù)netSetting 配置讓網(wǎng)絡(luò)方法內(nèi)部處理。但是某些場(chǎng)合不需要出現(xiàn)SVProgressHud,即使讀取的數(shù)據(jù)是從網(wǎng)絡(luò)獲取的也不出現(xiàn),只需要關(guān)閉netSetting對(duì)SVProgressHud的控制,但是自己也不添加SVProgressHud的控制就時(shí)間了。
緩存時(shí)間不由客戶端控制,需要交給服務(wù)器控制,無所謂,稍微調(diào)整網(wǎng)絡(luò)方法邏輯就能完成(就是現(xiàn)在這樣的)。
有些界面數(shù)據(jù)更新并不一定是非常頻繁的,但是需要更新的時(shí)候就必須更新,比如一個(gè)界面包含了用戶獲得的積分,在用戶消費(fèi)了積分后,再次進(jìn)入這個(gè)界面必須刷新數(shù)據(jù)。把這種類型的界面設(shè)置不緩存,并且在viewwillappear
中發(fā)送請(qǐng)求然后刷新UI無疑能解決問題。但是并不好,一個(gè)是因?yàn)檎?qǐng)求太頻繁了,浪費(fèi)流量,如果數(shù)據(jù)比較多,網(wǎng)絡(luò)比較慢,用戶體驗(yàn)很不好。但是通過上面介紹的緩存策略,將緩存時(shí)間改成一個(gè)極小的值,比如1秒,就能有效解決問題。因?yàn)槿绻麛?shù)據(jù)沒有發(fā)生變化雖然發(fā)起請(qǐng)求,服務(wù)器只返回簡(jiǎn)單提示:客戶端的數(shù)據(jù)是最新的,請(qǐng)求的數(shù)據(jù)很快。但是還是不好。因?yàn)閿?shù)據(jù)雖然在本地,但是UI還是會(huì)刷新一下,就是會(huì)閃一下。解決這樣的問題依然不難,可以在網(wǎng)絡(luò)配置類加一種類型:只要最新的數(shù)據(jù),在本地有緩存的情況下,僅僅在緩存過期并且數(shù)據(jù)確實(shí)發(fā)生變化的時(shí)候才把最新的數(shù)據(jù)用success塊回調(diào)給控制器,當(dāng)然和前面一樣這里要更新緩存數(shù)據(jù)和日期,其他情況不回調(diào)給控制器就解決了。
到此為止一個(gè)相對(duì)完整的網(wǎng)絡(luò)請(qǐng)求的框架完成,隨著需求的一步步明確,自己分析的透徹,才有了現(xiàn)在形勢(shì)。其實(shí)可以更進(jìn)一步優(yōu)化,可以在數(shù)據(jù)庫(kù)和數(shù)據(jù)之間在加一層緩存,也許會(huì)更好。自己后來看了一本叫做《圖解http協(xié)議》的書,發(fā)現(xiàn)里面好多緩存策略和上面介紹的很符合,所以以后可以參考下http協(xié)議里面的緩存策略的來擴(kuò)展網(wǎng)絡(luò)類。
更新:
實(shí)現(xiàn)代碼地址:https://github.com/xiaobai1993/HSNetTest,以后會(huì)逐步完善,減少對(duì)當(dāng)前項(xiàng)目業(yè)務(wù)的依賴。使得通用性更高。