iOS 工作中封裝通用性網(wǎng)絡(luò)請(qǐng)求框架

在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.太麻煩了,不能忍。


解決方案

  1. 我想我可以通過給網(wǎng)絡(luò)調(diào)用添加參數(shù)的形式方式擴(kuò)展功能,但是放棄了,因?yàn)槿绻o原來的方法添加一個(gè)參數(shù),工程所有的調(diào)用的地方都要改,實(shí)在麻煩,如果原來的方法保留,復(fù)制一下加個(gè)參數(shù)工程中會(huì)多出很多冗余代碼,而且參數(shù)過多調(diào)用起來也不方便。
  2. 自定義一個(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ù)的依賴。使得通用性更高。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,801評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,442評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,800評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,996評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評(píng)論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,233評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評(píng)論 1 286
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,702評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容