一逢并、前言
關(guān)于網(wǎng)絡(luò)層,蘋果對網(wǎng)絡(luò)請求部分已經(jīng)做了很好的封裝郭卫,業(yè)界的AFNetworking也被廣泛使用砍聊,除此以外,肯定還有其他的網(wǎng)絡(luò)框架贰军,但在實際的App開發(fā)中玻蝌,AFNetworking已經(jīng)成為了事實上各大App的標(biāo)準(zhǔn)配置。
我們一直都有講分層架構(gòu)词疼,其中很重要的一層就是網(wǎng)絡(luò)層俯树,那我們到底改如何設(shè)計才能更好的輔助我們的項目呢?最近也看了一些大牛的文章贰盗,也是有所獲许饿。
二、問題簡要
1.以什么方式將數(shù)據(jù)交付給業(yè)務(wù)層舵盈?
2.交付什么樣的數(shù)據(jù)給業(yè)務(wù)層陋率?
3.集約型API調(diào)用方式和離散型API調(diào)用方式
三、具體聊聊上面三個問題
1.以什么方式將數(shù)據(jù)交付給業(yè)務(wù)層秽晚?
iOS開發(fā)領(lǐng)域有很多對象間數(shù)據(jù)的傳遞方式翘贮,我看到的大多數(shù)App在網(wǎng)絡(luò)層所采用的方案主要集中于這三種:Delegate,Notification爆惧,Block。
我當(dāng)前開發(fā)的項目就是采用Block锨能。但我今天要講是以Delegate為主扯再,Notification為輔。原因如下:
1.盡可能減少跨層數(shù)據(jù)交流的可能址遇,限制耦合
2.統(tǒng)一回調(diào)方法熄阻,便于調(diào)試和維護
3.在跟業(yè)務(wù)層對接的部分只采用一種對接手段限制靈活性,以此來交換應(yīng)用的可維護性
盡可能減少跨層數(shù)據(jù)交流的可能倔约,限制耦合
什么叫跨層數(shù)據(jù)交流秃殉?就是某一層(或模塊)跟另外的與之沒有直接對接關(guān)系的層(或模塊)產(chǎn)生了數(shù)據(jù)交換。為什么這種情況不好浸剩?嚴(yán)格來說應(yīng)該是大部分情況都不好钾军,有的時候跨層數(shù)據(jù)交流確實也是一種需求。之所以說不好的地方在于绢要,它會導(dǎo)致代碼混亂吏恭,破壞模塊的封裝性。我們在做分層架構(gòu)的目的其中之一就在于下層對上層有一次抽象重罪,讓上層可以不必關(guān)心下層細節(jié)而執(zhí)行自己的業(yè)務(wù)樱哼。所以我們盡量不選擇Notification哀九。
然后為什么盡量不要用block?
block和delegate乍看上去在作用上是很相似,但是關(guān)于它們的選型有一條嚴(yán)格的規(guī)范:當(dāng)回調(diào)之后要做的任務(wù)在每次回調(diào)時都是一致的情況下搅幅,選擇delegate阅束,在回調(diào)之后要做的任務(wù)在每次回調(diào)時無法保證一致,選擇block
茄唐。
在離散型調(diào)用的場景下息裸,每一次回調(diào)都是能夠保證任務(wù)一致的,因此適用delegate琢融。這也是蘋果原生的網(wǎng)絡(luò)調(diào)用也采用delegate的原因界牡,因為蘋果也是基于離散模型去設(shè)計網(wǎng)絡(luò)調(diào)用的。
在集約型調(diào)用的場景下漾抬,使用block是合理的宿亡,因為每次請求的類型都不一樣,那么自然回調(diào)要做的任務(wù)也都會不一樣纳令,因此只能采用block挽荠。AFNetworking就是屬于集約型調(diào)用,因此它采用了block來做回調(diào)平绩。
統(tǒng)一回調(diào)方法圈匆,便于調(diào)試和維護
首先,Block本身無好壞對錯之分捏雌,只有合適不合適跃赚。在這一節(jié)要講的情況里,Block無法做到回調(diào)方法的統(tǒng)一性湿,調(diào)試和維護的時候也很難在調(diào)用棧上顯示出來纬傲。
在網(wǎng)絡(luò)請求和網(wǎng)絡(luò)層接受請求的地方時,使用Block沒問題肤频。但是在獲得數(shù)據(jù)交給業(yè)務(wù)方時叹括,最好還是通過Delegate去通知到業(yè)務(wù)方。因為Block所包含的回調(diào)代碼跟調(diào)用邏輯放在同一個地方宵荒,會導(dǎo)致那部分代碼變得很長汁雷,因為這里面包括了調(diào)用前和調(diào)用后的邏輯。從另一個角度說报咳,這在一定程度上違背了single function侠讯,single task
的原則,在需要調(diào)用API的地方暑刃,就只要寫API調(diào)用相關(guān)的代碼继低,在回調(diào)的地方,寫回調(diào)的代碼稍走。
然而大部分App里袁翁,當(dāng)我們寫代碼寫到這邊的時候柴底,也意識到了這個問題。因此我們會在block里面寫個一句話的方法接收參數(shù)粱胜,然后做轉(zhuǎn)發(fā)柄驻,然后就可以把這個方法放在其他地方了,繞過了Block的回調(diào)著陸點不統(tǒng)一的情況焙压。比如這樣:
[API callApiWithParam:param successed:^(Response *response){
[self successedWithResponse:response];
} failed:^(Request *request, NSError *error){
[self failedWithRequest:request error:error];
}];
這實質(zhì)上跟使用Delegate的手段沒有什么區(qū)別鸿脓,只是繞了一下,不過還是沒有解決統(tǒng)一回調(diào)方法的問題涯曲。Block是目前大部分第三方網(wǎng)絡(luò)庫都采用的方式野哭,因為在發(fā)送請求的那一部分,使用Block能夠比較簡潔幻件,因此在請求那一層是沒有問題的拨黔,只是在交換數(shù)據(jù)之后,還是轉(zhuǎn)變成delegate比較好绰沥,比如AFNetworking里面:
[AFNetworkingAPI callApiWithParam:self.param successed:^(Response *response){
if ([self.delegate respondsToSelector:@selector(successWithResponse:)]) {
[self.delegate successedWithResponse:response];
}
} failed:^(Request *request, NSError *error){
if ([self.delegate respondsToSelector:@selector(failedWithResponse:)]) {
[self failedWithRequest:request error:error];
}
}];
這樣在業(yè)務(wù)方這邊回調(diào)函數(shù)就能夠比較統(tǒng)一篱蝇,便于維護。
2.交付什么樣的數(shù)據(jù)給業(yè)務(wù)層徽曲?
其實我們的理想情況是希望API的數(shù)據(jù)下發(fā)之后就能夠直接被View所展示零截。但是,這怎么可能呢秃臣?UI可能一天一變涧衙,你能讓API也天天變?奥此,另外绍撞,這種做法使得View和API聯(lián)系緊密,也是我們不希望發(fā)生的得院。
這里我們引入了reformer(名字而已,叫什么都好)這個對象用于封裝數(shù)據(jù)轉(zhuǎn)化的邏輯章贞,這個對象是一個獨立對象祥绞,事實上,它是作為Adaptor模式存在的鸭限。我們可以這么理解:想象一下我們洗澡時候使用的蓮蓬頭蜕径,水管里出來的水是API下發(fā)的原始數(shù)據(jù)。reformer就是蓮蓬頭上的不同水流擋板败京,需要什么模式兜喻,就撥到什么模式。(其實它就是個專業(yè)處理數(shù)據(jù)的)
先定義一個protocol:
@protocol ReformerProtocol <NSObject>
- (ViewModel *)reformDataWithManager:(APIManager *)manager;
@end
在Controller里是這樣:
@property (nonatomic, strong) id<ReformerProtocol> XXXReformer;
@property (nonatomic, strong) id<ReformerProtocol> YYYReformer;
#pragma mark - APIManagerDelegate
- (void)apiManagerDidSuccess:(APIManager *)manager {
XXXViewModel *reformedXXXData = [manager fetchDataWithReformer:self.XXXReformer];
[self.XXXView configWithData:reformedXXXData];
YYYViewModel *reformedYYYData = [manager fetchDataWithReformer:self.YYYReformer];
[self.YYYView configWithData:reformedYYYData];
}
在APIManager里面赡麦,fetchDataWithReformer是這樣:
- (ViewModel *)fetchDataWithReformer:(id<ReformerProtocol>)reformer {
if (reformer == nil) {
return self.rawData;
} else {
return [reformer reformDataWithManager:self];
}
}
為了保持?jǐn)?shù)據(jù)可讀性朴皆,我們這里再引入一個ViewModel帕识,其實就是相關(guān)View的模型。一個控件對應(yīng)一個屬性遂铡。
PropertyListReformer.m
- (PropertListCellModel *)reformData:(NSDictionary *)originData fromManager:(APIManager *)manager {
... ...
... ...
PropertListCellModel *resultData = nil;
if ([manager isKindOfClass:[FirstListAPIManager class]]) {
resultData = ...
}
if ([manager isKindOfClass:[SecondListAPIManager class]]) {
resultData = ...
}
if ([manager isKindOfClass:[ThirdListAPIManager class]]) {
resultData = ...
}
return resultData;
}
PropertListCell.m
- (void)configWithData:(PropertListCellModel *)model {
self.imageView.image = model.image;
self.idLabel.text = model.id
self.nameLabel.text = model.name;
self.titleLabel.text = model.title;
}
3.集約型API調(diào)用方式和離散型API調(diào)用方式
集約型API調(diào)用其實就是所有API的調(diào)用只有一個類肮疗,然后這個類接收API名字,API參數(shù)扒接,以及回調(diào)著陸點(可以是target-action伪货,或者block,或者delegate等各種模式的著陸點)作為參數(shù)钾怔。然后執(zhí)行類似startRequest這樣的方法碱呼,它就會去根據(jù)這些參數(shù)起飛去調(diào)用API了,然后獲得API數(shù)據(jù)之后再根據(jù)指定的著陸點去著陸宗侦。
集約型API調(diào)用方式:
[APIRequest startRequestWithParams:params success:^(Response *response){
...
} fail:^(Error *error){
...
}];
離散型API調(diào)用是這樣的愚臀,一個API對應(yīng)于一個APIManager,然后這個APIManager只需要提供參數(shù)就能起飛凝垛,API名字懊悯、著陸方式都已經(jīng)集成入APIManager中。
離散型API調(diào)用方式:
@property (nonatomic, strong) ItemListAPIManager *itemListAPIManager;
// getter
- (ItemListAPIManager *)itemListAPIManager {
if (!_itemListAPIManager) {
_itemListAPIManager = [[ItemListAPIManager alloc] init];
_itemListAPIManager.delegate = self;
}
return _itemListAPIManager;
}
// 使用的時候就這么寫:
[self.itemListAPIManager loadDataWithParams:params];
- (void)successWithResponse:(Response *)response { ... }
- (void) failedWithResponse:(Error *) error { ... }
四梦皮、總結(jié)
1.使用delegate來做數(shù)據(jù)對接炭分,僅在必要時采用Notification來做跨層訪問
2.交付NSDictionary給業(yè)務(wù)層,使用Const字符串作為Key來保持可讀性
3.提供reformer機制來處理網(wǎng)絡(luò)層反饋的數(shù)據(jù)剑肯,這個機制很重要捧毛,好處極多
4.網(wǎng)絡(luò)層上部分使用離散型設(shè)計,下部分使用集約型設(shè)計
五让网、寫在最后
注:本文主體思想是學(xué)習(xí)了Casa Taloyum 的一篇關(guān)于網(wǎng)絡(luò)框架的文章呀忧,很有收獲,于是有了一些學(xué)習(xí)感悟溃睹,也感謝大牛的文章而账。并附上原文如下:
iOS應(yīng)用架構(gòu)談 網(wǎng)絡(luò)層設(shè)計方案