好久沒寫部落格了。我的部落格地址。之前一直在研究MVVM這種新的開發模式。也算是沉澱了一段時間,國慶期間可能還會寫寫MVVM。今天要聊的是創萌工作室的iOS客戶端網路請求的封裝。因為很多原因封裝的還不夠好存在很多問題今天寫出來只是想把最近做的事情寫出來作個記錄。
我寫這篇文章已經做好了被噴死吐槽死的準備了,因為我感覺我封裝的太有問題了,但是又真的很想寫一寫,畢竟對我來說確實解決了一個大問題。
先簡單的說說我們的網路請求轉變過程
加入工作室一年。一共做了有三四個專案了。我先大概敘述一下我們的網路請求的轉變過程
第一階段 直接呼叫AFNetworking
第一個專案那時候懵懂無知,每個介面都直接調AFNetworking。這樣導致的問題就是程式碼量驟然增加。
第二階段 直接呼叫YTKNetwork
第二個專案剛做的時候唐巧開源了他們在猿題庫封裝的YTKNetwork。那時候還是懵懂無知,還是在每個介面都開始直接調YTKNetwork。人家封裝的那麼好的東西就被我用成這個栽子,簡直對不起巧大。我記得有個介面好像有5條網路請求,可以想象程式碼的冗餘度。
第三階段 開始使用ReactiveCocoa
因為在做第二個專案的時候我和迪哥負責不同的客戶端。迪哥在看了limboy的基於AFNetworking2.0和ReactiveCocoa2.1的iOS REST Client開始將網路請求剝離到一個專門的介面,這樣每次就不用寫很多的東西了。
limboy用了AFNetworking-RACExtensions來實現回撥的效果。簡單的說就是subscribe一個訊號,然後訊號會返回一個訊號回來,這樣就實現了將網路請求部分剝離的效果。
我在第二個專案基本上結束的時候上線了一個自己獨立開發的app,裡面就是用了這樣的方法。
這學期在做專案的時候還是在沿用迪哥的程式碼,但是我發現了很多問題。
一是limboy在寫rac程式碼的時候用的concat無法完成網路請求再請求。簡單的說,伺服器在返回告訴我session失效的時候我先需要後臺自動登入然後再次網路請求。我再寫的時候concat無法實現再次網路請求,我也不太明白為什麼,試了各種方法都不行。這是很困惑我的我後面還需要再次研究一下。
二是再判斷伺服器返回的東西的時候,需要判斷狀態碼。如果成功,那麼會有json資料返回回來,如果失敗則沒有資料。出現這樣的情況如果我在Acontroller調Bcontroller的網路請求則還需要判斷是不是返回了一個陣列或者物件,如果是,開始對資料進行處理,如果不是,還得重新進行網路請求,因為說明session失效了。
這樣還是導致了網路請求部分有大量的程式碼。
根據Coding的iOS重新封裝網路請求
Coding的iOS客戶端是開源的,在Github和Coding官網都有。我放的連結是一個下載下來就能跑起來的。(強迫症,跑不起來的程式碼都不想看..不過現在看MVVM好多都跑不起來也硬著頭皮看了)
Coding的網路請求自己看了。Coding是用block來進行回撥的。至於這一塊選擇notice還是block還是delegate,可以參考iOS應用架構談 網路層設計方案我算是認真看了,但是不是很能寫的出來…
插一句話,我們為什麼不用block。因為迪哥也不太會block就直接上rac了,我之前的部落格寫過簡單的block,我在寫程式碼的時候用delegate和notice比較多所以對block的實踐比較少。這是我自身的問題。而且說實話我覺得用rac挺好的,因為block加上typedef啥的其實很多東西的,不像rac直接調就完事了。
下面開始今天的算是乾貨的東西
1. 分析Coding網路請求
Coding的網路請求我覺得可以總結為
block -> block -> AFNetworking
下面來解釋一下,首先第一個block是我們的主viewcontroller,也就是我們邏輯部分和檢視部分。首先第一個block調Coding_NetAPIManager裡的函式。然後在Coding_NetAPIManager再調CodingNetAPIClient裡的函式。
(具體Coding程式碼請看Coding客戶端程式碼的連結)
我們倒著來說。
第三部分 AFNetworking
在我分類的AFNetworking裡也就是CodingNetAPIClient裡,Coding進行了一件事情,那就是進行AFNetworking的網路請求。
在獲取到資料的時候的對reponse進行一個判斷。在判斷資料的時候,如果資料有錯誤,則直接顯示錯誤的msg,如果沒有錯誤,那麼則不返回任何東西。
然後在網路請求中判斷,如果有錯誤,那麼返回nil和id型別的error。如果沒錯誤,返回response和nil。
第二部分 block
在這部分裡,回撥的結果有兩種,一種是有資料,一種是沒資料。其實到這就行了。那麼現在在這第二個部分幹什麼呢,json轉model。就這麼簡單。返回的東西,如果有資料返回,那麼就再次返回model或者是data和nil,如果沒有資料返回,就返回nil和error。
第一部分 block
到這裡,其實只要判斷有無資料就可以啦。
好了。下面我們只需要用RAC來替換block就完成了。當然了,中間有坑,不會那麼簡單的…
2. 改寫網路請求
我要上程式碼了。依然三部分。我們還是倒著來。我放關鍵的程式碼在這。
既然我把Coding的程式碼分成了
block -> block -> AFNetworking
那麼我的基本上就可以說是
RAC -> RAC -> AFNetworking
也是倒著來。
第三部分 AFNetworking
這是AFNetworking網路請求
//一切仿照Coding
case Get:{
return [[[[self rac_GET:aPath parameters:params] map:^id(RACTuple *JSONAndHeaders) {
NSDictionary *responseObject = JSONAndHeaders[0];
DebugLog(@"
===========response===========
%@:
%@", aPath, responseObject);
//這裡呼叫下一個部分的函式
id error = [self handleResponse:responseObject autoShowError:autoShowError rerequestJsonDataWithPath:aPath withParams:params withMethodType:method];
if (error) {
return RACTuplePack(nil, error);
}else{
return RACTuplePack(responseObject, nil);
}
}]
catch:^RACSignal *(NSError *error) {
DebugLog(@"
===========response===========
%@:
%@", aPath, error);
return [self showError:error];
}] replayLazily];
break;
}
-(id)handleResponse:(id)responseJSON autoShowError:(BOOL)autoShowError
rerequestJsonDataWithPath:(NSString *)aPath
withParams:(NSDictionary*)params
withMethodType:(NetworkMethod)method{
NSError *error = nil;
NSNumber *resultCode = [responseJSON valueForKeyPath:@"status"];
//如果伺服器返回的值不是正確有數值的話
if (resultCode.intValue != VALUE) {
error = [NSError errorWithDomain:BASE_URL code:resultCode.intValue userInfo:responseJSON];
//如果伺服器返回session失效的錯誤碼
if (resultCode.intValue == VALUE) {//使用者未登入
[[[NetWork sharedManager] login] subscribeNext:^(RACTuple *x) {
RACTupleUnpack(id data) = x;
//由於沒登陸那麼這裡呼叫第二個部分RAC的登陸方法,進行重新登陸
if (data) {
//這時有資料返回則再次發出網路請求
[self rerequestJsonDataWithPath:aPath withParams:params withMethodType:method];
} else {
}
}];
}else{
if (autoShowError) {
[self showError:error];
}
}
}
return error;
}
//這是重新登陸後再次進行網路請求
- (void)rerequestJsonDataWithPath:(NSString *)aPath
withParams:(NSDictionary*)params
withMethodType:(NetworkMethod)method
{
[[[NetWorkCheck sharedJsonClient] requestJsonDataWithPath:aPath withParams:params withMethodType:Get] subscribeNext:^(id x) {
NSLog(@"success");
}];
}
第二部分 RAC
- (RACSignal *)test2
{
NSString *path = @"/MyList.do";
NSDictionary *params = @{@"id":@"22"};
return [[[NetWorkCheck sharedJsonClient] requestJsonDataWithPath:path withParams:params withMethodType:Get] map:^id(RACTuple *x) {
RACTupleUnpack(id resultData, NSError *error) = x;
if (resultData) {
return RACTuplePack(resultData, nil);
} else {
return RACTuplePack(nil, error);
}
}];
}
第一部分 RAC
- (IBAction)test2:(id)sender
{
[[[NetWork sharedManager] test2] subscribeNext:^(RACTuple *x) {
RACTupleUnpack(id data) = x;
if (data) {
}
}];
}
看程式碼的時候最好是從第一部分看,我為了突出重點所以把第三部分放到最前面了。基本上我就幹了一件事情,把block改寫為RAC。
3. 問題以及填坑
問題一:回撥兩個值
其實用RAC改寫block是不難的,難的在於block傳值傳了兩個回去,RAC我沒找到可以傳兩個值的地方,於是我用了RACTuplePack,這個是RAC裡一個巨集定義,可以打包變數。然後在訊號收取端利用RACTupleUnpack(id resultData, NSError *error) = x;
來解加壓縮變數(用詞不準確見諒)
在這裡非常感謝這樣好用的ReactiveCocoa,根本停不下來這篇部落格。看到了RACTuplePack這個巨集定義。
問題二:如何在伺服器返回session失效之後進行兩次連結請求,一次後臺自動登入,一次重新進行網路請求
在Coding的程式碼裡,我看到如果未登入是會彈出登入介面。但是我們要求是後臺登入然後重新請求。
我開始是想在get請求那部分直接在此調get的網路請求。但是不會執行兩次網路請求。
之前的專案是在vc的介面判斷沒資料則在此呼叫函式。如果那樣的話我不是白封裝半天了…於是決定封裝到network裡,我就在重新登入後判斷有無資料,有資料則意味著登入成功,登入成功,在此調一個登入的函式。
雖然這樣看上去就不合理,但是是我能嘗試出來的一個辦法了…嘗試了好幾天,查了半天也沒有類似的解決方案。
問題三:為什麼有時候不會執行網路請求
我在解決問題二的時候就出現這種問題,不能進行網路請求,我只是一個簡單的調函式。但是,但是,但是,在rac裡必須要subscribeNext,如果不subscribeNext則不會調!這是需要記住的。
4. 還存在的問題以及不足
-
Coding對於Get請求還做了快取,我沒做。後面會慢慢加上。
-
RAC是對於很多東西的一個大集合,比如block比如KVO等等。所以需要對iOS的記憶體管理機制進行一個深入理解,這是我一直所欠缺的。這個問題在我解決問題二的時候出現了好幾次報錯,都是這個問題。但是我卻無法解決。
-
Coding中還用到了很多顯示錯誤的MBProgress等等,我在此都沒寫,如果想仔細研究去看Coding的原始碼。
-
Coding的網路請求還有很多對檔案的處理,post請求等等,我現在只改寫了Get和Post請求。後面需要把Coding這一套都好好的研究一下。
-
對於RAC的理解還是不夠,在整個過程中遇到很多問題。
最後的最後感謝Coding將他們的客戶端開源出來,感謝為Coding貢獻程式碼的工程師。是你們讓我學到了更多的東西?