APP重構(gòu)之路 網(wǎng)絡(luò)請求框架

APP重構(gòu)之路 網(wǎng)絡(luò)請求框架
APP重構(gòu)之路 Model的設(shè)計

前言

在現(xiàn)在的app大审,網(wǎng)絡(luò)請求是一個很重要的部分铃诬,app中很多部分都有或多或少的網(wǎng)絡(luò)請求祭陷,所以在一個項目重構(gòu)時,我會選擇網(wǎng)絡(luò)請求框架作為我重構(gòu)的起點趣席。在這篇文章中我所提出的架構(gòu)兵志,并不是所謂的 最好 的網(wǎng)絡(luò)請求架構(gòu),因為我只基于我這個app原有架構(gòu)進行改善宣肚,更多的情況下我是以app為出發(fā)點想罕,讓這個網(wǎng)絡(luò)架構(gòu)能夠在原app的環(huán)境下給我一個完美的結(jié)果,當(dāng)然如果有更好的改進意見霉涨,我會很樂于嘗試按价。

關(guān)于網(wǎng)絡(luò)請求框架

一個好的網(wǎng)絡(luò)請求框架對于一個團隊來說是十分重要的。如果一個網(wǎng)絡(luò)請求框架沒有封裝好笙瑟,或者是在設(shè)計上存在問題楼镐,那么在開發(fā)上會造成許多問題,就拿這段代碼作為例子:

[leaveAPI startWithCompletionBlockWith:^(BaseRequest *baseRequest, id responseObject) {
              //check the response object
            BOOL isSuccess = [leaveAPI validResponseObject:responseObject];
            if (isSuccess) {
                    //do something...
            }
            
        } failure:^(BaseRequest *baseRequest) {
                    //do something...
        }];

上面這段代碼存在著不少的問題往枷,比如把請求數(shù)據(jù)的判斷放到了每一個請求中框产、在leaveAPI的塊方法中再次調(diào)用leaveAPI、塊參數(shù)中的baseRequest并沒有實質(zhì)作用等等……針對這些問題我會一一進行修正错洁。

不要讓其他人做請求數(shù)據(jù)有效與否的判斷

在上面的代碼中茅信,對resposeObject是否有效的判斷被設(shè)計成了BaseRequest類中的一個方法,程序員需要在調(diào)用網(wǎng)絡(luò)請求后墓臭,再調(diào)用該方法對responseObject進行判斷蘸鲸,這樣的設(shè)計存在很大的弊端。

在實際應(yīng)用中窿锉,很多時候程序員在調(diào)用網(wǎng)絡(luò)請求后往往會忘記調(diào)用該方法對返回結(jié)果進行判斷酌摇,甚至忘記了存在這個方法膝舅,自行對responseObject進行判斷。首先這造成了大規(guī)模的代碼重復(fù)窑多,另一方面仍稀,不同程序員自己編寫的判斷方法散落在各個請求中,假如app在日后更新過程中改變了這個判斷標(biāo)準埂息,會給修改帶來很大困難技潘。

注意在塊方法中的循環(huán)調(diào)用

上面的代碼中,在leaveAPI的塊方法中千康,再次調(diào)用了leaveAPI中的方法享幽,這樣導(dǎo)致了“retain cycle“,實際上正確的調(diào)用方法應(yīng)該是:

[leaveAPI startWithCompletionBlockWith:^(LeaveAPI *api, id responseObject) {
              //check the response object
            BOOL isSuccess = [api validResponseObject:responseObject];
            if (isSuccess) {
                    //do something...
            }
        }];

為什么會出現(xiàn)這樣的情況拾弃,首先主要是因為整個請求框架的注釋不清晰值桩,導(dǎo)致其他程序員對方法的理解存在偏差,進而天馬行空豪椿,發(fā)揮自己的想象力來調(diào)用方法奔坟。另外由于各個API與BaseRequest的設(shè)計上存在問題,導(dǎo)致整個網(wǎng)絡(luò)請求框架的混亂搭盾。

不要在單獨的API中實現(xiàn)上傳下載操作

在舊的網(wǎng)絡(luò)請求框架中咳秉,BaseRequest一開始的設(shè)計中并沒有針對上傳和下載操作進行處理,而且整個BaseRequest的設(shè)計中并沒有AOP鸯隅,這個導(dǎo)致了在日后需要增加上傳和下載功能的時候只能將他們寫到單獨的API中澜建,這個導(dǎo)致了代碼重復(fù),代碼的復(fù)用性降低滋迈,如:

//
//  FileAPI.m
//

...some methods...

#pragma mark - Upload & Download

-(void)uploadFile:(FileUploadCompleteBlock)uploadBlock errorBlock:(FileUploadFailBlock)errorBlock {
    NSString *url = self.url   
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    manager.requestSerializer = [AFHTTPRequestSerializer serializer];
    manager.operationQueue.maxConcurrentOperationCount = 5;
    manager.requestSerializer.timeoutInterval = 30;
    manager.responseSerializer.acceptableContentTypes =  [NSSet setWithObjects:@"application/json", @"text/html",@"text/json",@"text/javascript",@"text/plain",nil];
    
    [manager POST:url parameters:[self requestArgument] constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
    
      // upload operation ...
      
    }success:^(AFHTTPRequestOperation *operation, id responseObject) {
        
     // do something ...
    }failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    
      // do something ...
    }];
}

FileAPI.m中霎奢,上傳操作是這樣實現(xiàn)的户誓。寫下這段代碼的時候是使用AFNetworking 2.0饼灿,而現(xiàn)在使用的是AFNetworking 3.0AFHTTPRequestOperationManager也變成了AFHTTPSessionManger帝美,這個時候散落在各個API的上傳方法修改起來就變的很麻煩碍彭。

BaseRequest中的設(shè)計缺陷

在上文中一直在指出各個API中的缺陷,而也提到很多地方是歸咎于BaseReuqest的問題悼潭,現(xiàn)在就來看一下它里面的一些缺陷:

首先在整個BaseRequest中庇忌,它包括了地址的組裝、網(wǎng)絡(luò)環(huán)境的判斷舰褪、請求的發(fā)送等等皆疹,基本網(wǎng)絡(luò)請求的所有操作都是由這一個類來實現(xiàn)。這樣就導(dǎo)致了整個類十分龐大占拍,在需要添加新的請求類型如我上文提到的上傳與下載時略就,會難以下手捎迫,這就導(dǎo)致了我上文提到的種種問題。

另一方面BaseRequest中沒有針對返回數(shù)據(jù)的處理表牢,這里的處理是指返回數(shù)據(jù)的緩存操作窄绒、數(shù)據(jù)過濾操作、請求數(shù)據(jù)為空的處理操作等等崔兴,如果這些問題都交給方法調(diào)用者來完成的話彰导,會導(dǎo)致某一模塊的代碼量暴漲(在本app是VC),而且很多時候數(shù)據(jù)需要的只是一個默認的緩存操作敲茄、默認的過濾操作位谋,這個時候重復(fù)性的代碼會很多,倒不如把這些操作統(tǒng)一處理好折汞,假如有特殊的API需要進行特殊的配置倔幼,再由該API對這些配置進行修改,而不需要把這些默認操作交由其他程序員來完成爽待。

我是如何設(shè)計新的網(wǎng)絡(luò)請求框架

上文提到了各種各樣的不足损同,所以是時候針對這些不足進行改進了。

先看大局鸟款,再看細節(jié)膏燃。首先是整個架構(gòu)的數(shù)據(jù)流向:

網(wǎng)絡(luò)請求架構(gòu)-數(shù)據(jù)流.jpg

整個網(wǎng)絡(luò)請求框架中最重要的是其中的NetworkManage,它主要是負責(zé)整個請求的處理何什。

網(wǎng)絡(luò)請求架構(gòu)-請求過程.jpg

設(shè)計中的一些關(guān)注重點

首先檢測網(wǎng)絡(luò)狀態(tài)

當(dāng)一個請求發(fā)起的時候组哩,首先它會檢測網(wǎng)絡(luò)是否聯(lián)通,假如沒有聯(lián)通的時候會直接彈出一個窗口提醒用戶需要先連接網(wǎng)絡(luò)处渣,而不會進行下一步的請求伶贰。而在舊的網(wǎng)絡(luò)請求框架中,很多時候把這段代碼放到了vc罐栈,現(xiàn)在將它整合進來黍衙。

- (void)addRequest:(BaseRequest*)request {
    
    //TODO: 檢查網(wǎng)絡(luò)是否通暢
    if(![self checkNetworkConnection])
    {
        [self showNetworkAlertForRequest:request];
        return;
    }

[self checkNetworkConnection]:

- (BOOL)checkNetworkConnection
{
    struct sockaddr zeroAddress;
    bzero(&zeroAddress, sizeof(zeroAddress));
    zeroAddress.sa_len = sizeof(zeroAddress);
    zeroAddress.sa_family = AF_INET;
    
    SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
    SCNetworkReachabilityFlags flags;
    
    BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
    CFRelease(defaultRouteReachability);
    
    if (!didRetrieveFlags) {
        printf("Error. Count not recover network reachability flags\n");
        return NO;
    }
    
    BOOL isReachable = flags & kSCNetworkFlagsReachable;
    BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired;
    return (isReachable && !needsConnection) ? YES : NO;
}

活性組裝請求地址

而在進行完網(wǎng)絡(luò)聯(lián)通的判斷之后,就會對請求的地址進行組裝荠诬。組裝地址的方法并沒有太大的變化琅翻,但是在舊的請求框架開發(fā)的時候,我注意到一個問題:在增加新需求增加新的接口的時候柑贞,往往需要連接到測試服務(wù)器上進行調(diào)試方椎,這時候就需要將請求的地址改成測試服務(wù)器的地址。但這往往引發(fā)一些問題钧嘶,因為測試服務(wù)器上可能沒有正式服務(wù)器的一些數(shù)據(jù)棠众,在測試時往往沒有問題,但是轉(zhuǎn)移到正式服務(wù)器上就出現(xiàn)了各種問題有决,所以我就想能不能改成程序員可以改變API連接的地址闸拿,而不改變?nèi)值恼埱罂蚣芙瘟粒尭鱾€API在請求的時候判斷自己是否需要連接到測試服務(wù)器。

- (NSString *)urlString{
    NSString *url = nil;
    //TODO: 使用副地址
    if ([self.child respondsToSelector:@selector(useViceUrl)] && [self.child useViceUrl]){
        baseUrl = self.config.viceBaseUrl;
    }
    //TODO: 使用主地址
    else{
        baseUrl = self.config.mainBaseUrl;
    }
}

讓API能夠獨立配置

組裝地址完畢之后胸墙,就開始根據(jù)API自身的設(shè)置來進行配置我注,在舊的請求框架中,API的是直接繼承自BaseRequest這個類迟隅,導(dǎo)致了BaseRequest需要完成大量的工作但骨,或是存有大量空方法,可讀性與穩(wěn)定性都很差智袭,很多東西也沒有辦法讓API自己進行獨立設(shè)置奔缠。在新的框架中,我選擇將API的設(shè)置通過一個叫做APIProtocol的協(xié)議來完成吼野,API需要配置的內(nèi)容可以通過實現(xiàn)該協(xié)議的方法來進行配置校哎,否則就會直接使用默認配置

//TODO: 檢查是否使用自定義超時時間
    if ([request respondsToSelector:@selector(requestTimeoutInterval)]) {
        self.manager.requestSerializer.timeoutInterval = [request requestTimeoutInterval];
    }
    else{
        self.manager.requestSerializer.timeoutInterval = 60.0;
    }
    
    more methods ...

完善返回數(shù)據(jù)的基礎(chǔ)判斷

最后在進行完請求判斷后,將會對responseObject的有效性進行判斷瞳步。關(guān)于數(shù)據(jù)的判斷我一開始是打算放在BaseRequest中的闷哆,因為一開始的想法是希望能夠在BaseRequest中做一個默認的判斷,假如API自身需要再度對responseObject進行進一步的判斷時单起,可以通過協(xié)議方法來重新編寫該API獨立的判定方法抱怔。但這種方法最終被我棄用了,首先responseObject的基礎(chǔ)判斷在我看來是不應(yīng)該放在BaseRequest中的嘀倒,因為BaseRequest是作為一個請求的"中心"屈留,不應(yīng)該把數(shù)據(jù)處理的問題交給它處理。另一方面是因為我們需要設(shè)計的是基礎(chǔ)判斷测蘑,它和各個API獨立的判斷方式不是平行關(guān)系灌危,而是層次關(guān)系,因為在設(shè)計的是每一個API都需要進行的判斷碳胳,假如在整個app中有很多API需要進行獨立判斷勇蝙,就意味著需要編寫很多次基礎(chǔ)判斷邏輯,同時假如在日后需要修改這個基礎(chǔ)判斷內(nèi)容固逗,代碼也散落在各個地方浅蚪,這不是我們想要的結(jié)果藕帜。

所以在設(shè)計上我最終把這個判斷方法放到了NetworkConfig中烫罩,新增了一個BaseFilter類,專門用于返回數(shù)據(jù)的判斷洽故,假如我的API需要增加獨特的判斷方法時贝攒,可以直接在請求方法中直接對responseObject進行進一步判斷。

NetworkConfig.m:

//NetworkManage.m

if([self.networkConfig.baseFilter validResponseObject:responseObject])
{
    request.responseObject = responseObject;
    [self handleSuccessRequest:task];
}
else
{
    NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadServerResponse userInfo:nil];
    request.responseObject = responseObject;
    [self handleFailureRequest:task error:error];
}

BaseFilter.m

@implementation BaseFilter

- (BOOL)validResponseObject:(id)responseObject
{
    //TODO: 檢查是否返回了數(shù)據(jù)且數(shù)據(jù)是否正確
    if (!responseObject && ![responseObject isKindOfClass:[NSDictionary class]] && ![responseObject[@"success"] boolValue]) {
        return NO;
    }
    else
        return YES;
}

@end

結(jié)語

我相信在軟件設(shè)計中并不存在最好或者是最正確的架構(gòu)时甚,因為這是一個很抽象的工作隘弊,但我相信我們應(yīng)該可以設(shè)計出一個擴展性良好簡單明了的架構(gòu)哈踱,能夠讓新加入的程序員快速上手,能夠適應(yīng)軟件接下來的開發(fā)需要梨熙,那這大概是一個好的架構(gòu)开镣。


想了解更多內(nèi)容可以查看我的主頁

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市咽扇,隨后出現(xiàn)的幾起案子邪财,更是在濱河造成了極大的恐慌,老刑警劉巖质欲,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件树埠,死亡現(xiàn)場離奇詭異,居然都是意外死亡嘶伟,警方通過查閱死者的電腦和手機怎憋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來九昧,“玉大人绊袋,你說我怎么就攤上這事≈ィ” “怎么了愤炸?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長掉奄。 經(jīng)常有香客問我规个,道長,這世上最難降的妖魔是什么姓建? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任诞仓,我火速辦了婚禮,結(jié)果婚禮上速兔,老公的妹妹穿的比我還像新娘墅拭。我一直安慰自己,他們只是感情好涣狗,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布谍婉。 她就那樣靜靜地躺著,像睡著了一般镀钓。 火紅的嫁衣襯著肌膚如雪穗熬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天丁溅,我揣著相機與錄音唤蔗,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛妓柜,可吹牛的內(nèi)容都是我干的箱季。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼藏雏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了作煌?” 一聲冷哼從身側(cè)響起诉稍,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎最疆,沒想到半個月后杯巨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡努酸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年服爷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片获诈。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡仍源,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舔涎,到底是詐尸還是另有隱情笼踩,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布亡嫌,位于F島的核電站嚎于,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏挟冠。R本人自食惡果不足惜于购,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望知染。 院中可真熱鬧肋僧,春花似錦、人聲如沸控淡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掺炭。三九已至辫诅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背愧膀。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工鹅巍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留逸邦,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓废离,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子钢颂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

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