[iOS]自己實現(xiàn)一個簡單的離散化網(wǎng)絡(luò)請求庫

更新:梳理了庫中的耦合文件,可以直接提取網(wǎng)絡(luò)庫文件夾進(jìn)行使用,優(yōu)化了緩存設(shè)計.
鳴謝:本人是在認(rèn)真研讀casa的iOS應(yīng)用架構(gòu)談 網(wǎng)絡(luò)層設(shè)計方案之后,得出的相應(yīng)的思路,并在此基礎(chǔ)上做出了自己的需求延展.在此十分感謝這位反革命工程師的真知灼見!

首先,什么是離散化管理?在鳴謝的這篇文章里,casa已經(jīng)做出了一個比較明確的解釋:每一個請求的API都對應(yīng)一個類來管理,不同于將請求的url,參數(shù)等都放入一個方法(又稱集約式管理)中來管理.好處顯而易見:便于維護(hù),控制.

集約式是這樣的:

[manager GET:url parameters:param progress:nil success:^(NSURLSessionDataTask * _Nonnull task, NSDictionary* responseObject) {
        success ? success(responseObject) : nil;
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        failure ? failure(error) : nil;
        NSLog(@"請求失敗-->%@",error);
    }];

離散化是這樣的:

@implementation TestAPIManger
//請求方式
- (EWRequestType)requestType{
    return EWAPIRequestTypeGet;
}
//請求的方法名
- (NSString *)requestMethod{
    return @"video";
}
//請求需要的參數(shù)
- (NSDictionary *)params{
    return @{@"type":@"JSON"};
}
//是否需要緩存
- (NSNumber *)shouldCache{
    return @180;
}
//額外參數(shù),根據(jù)需求而定
- (NSString *)memberCode{
    return @"";
}
//是否需要加載動畫
- (NSDictionary *)animationTargetAction{
    return @{
             EWRequestAnimationTarget : @"ZNRequestAnimation",
             EWShowHudAnimation : @"showHudAnimation",
             EWHideHudAtWindow : @"hideHudAtWindow"
             };
}
//是否需要拼接請求頭
- (NSDictionary *)headerDict{
    return nil;
}
@end

離散化的調(diào)用方法是這樣的,這里使用代理的方式來回調(diào)結(jié)果,目的是控制靈活性,方便管理,bug排查:

TestAPIManger *testApi = [[TestAPIManger alloc] init];
testApi.delegate = self;
[testApi loadData];

這里來進(jìn)行一波解釋:
TestAPIManger->進(jìn)行網(wǎng)絡(luò)請求的實例,封裝了請求需要的url,參數(shù)等
testApi.delegate->進(jìn)行網(wǎng)路請求數(shù)據(jù)回調(diào)的代理
loadData->開啟網(wǎng)絡(luò)請求的方法.

正式開始封裝之路:
疑問1:如何設(shè)計這個APIManager?
這里是設(shè)計了一個EWAPIBaseManager,這個類的作用是定義請求APIManager請求的基本方法,作為一個父類,之后的每個請求實例都繼承自這個類.
這里設(shè)計出來的樣子暫時是這個樣子:

//回調(diào)的代理,需要遵守EWAPICallBackProtocol協(xié)議
@property (nonatomic , weak) id<EWAPICallBackProtocol> delegate;

//遵守協(xié)議的子類,須遵守EWAPIManagerProtocol協(xié)議
@property (nonatomic , weak) NSObject<EWAPIManagerProtocol> *childManager;
//自定義response用來統(tǒng)一保存數(shù)據(jù)和error
@property (nonatomic , strong) EWResponse *response;

//外部傳入的參數(shù)
@property (strong,nonatomic) NSMutableDictionary *outerParams;

//是否需要動畫
@property (nonatomic , strong) NSDictionary *animationTargetAction;

//數(shù)據(jù)過濾的方法,必須要遵守EWDataFilterProtocol協(xié)議

- (id)filterDataWithFilter:(id<EWDataFilterProtocol>)filter;

//是否需要緩存
- (NSNumber *)shouldCache;
/**
 * 加載數(shù)據(jù)
 */
- (void)loadData;
/**
 * 取消請求
 */
- (void)cancelAllRequest;

其中加載數(shù)據(jù)作為一個基本功能被放到了這里,方法名為loadData.
這里用到了幾個協(xié)議:

EWAPICallBackProtocol:完成請求回調(diào)的協(xié)議
EWAPIManagerProtocol:管理每個請求的參數(shù),url等
EWDataFilterProtocol:定義了數(shù)據(jù)過濾的方法

疑問2:每一個APIManager如何管理請求URL和參數(shù)?
解決方式:聲明一個協(xié)議EWAPIManagerProtocol,聲明如下方法

typedef NS_ENUM(NSInteger,EWRequestType){
    EWAPIRequestTypeGet = 0,
    EWAPIRequestTypePost = 1,
    EWAPIRequestTypeUploadImage = 2
};
@protocol EWAPIManagerProtocol <NSObject>
@required
//請求方式
- (EWRequestType)requestType;
//請求的參數(shù)
- (NSDictionary *)params;
//請求的方法名
- (NSString *)requestMethod;
//請求后完整的拼接參數(shù)
- (NSDictionary*)paramsForAPI;
//自定義的requestheader
- (NSDictionary *)headerDict;
@optional
//物業(yè)接口可能會有membercode
- (NSString *)memberCode;
@end

然后創(chuàng)建一個NSObject類,遵守這個協(xié)議,就是上面的TestAPIManger,并實現(xiàn)協(xié)議中的方法,那么這些參數(shù)都被保存在了這個TestAPIManger了.

疑問3:如何將APIManager保存的參數(shù)傳遞到網(wǎng)絡(luò)請求中?
這里我要重提一下casa的觀點(diǎn),離散化的網(wǎng)絡(luò)層其實本質(zhì)是集約調(diào)用,我們只不過是在底層通過delegate將返回的數(shù)據(jù)進(jìn)行了轉(zhuǎn)發(fā)而已,因為底層變動不大,所以如此做無傷大雅.

在上面的loadData方法中,是如下實現(xiàn)方式:

/**
 * 執(zhí)行請求任務(wù)
 */
- (void)loadData{
    switch (self.childManager.requestType) {
            
        case EWAPIRequestTypeGet:
            APIRequest(Get)
            break;
            
        case EWAPIRequestTypePost:
            APIRequest(Post)
            break;
            
        case EWAPIRequestTypeUploadImage:
            APIRequest(PostImage)
            break;
        default:
            break;
    }
}

其中APIRequest()是一個宏,該宏實現(xiàn)了網(wǎng)絡(luò)請求:

/**
 * 定義完成請求的宏
 */
#define APIRequest(requestType) \
{\
EW_WeakSelf\
    [self startRequestAnimation];\開啟動畫
[self.apiRequest sendRequestBy##requestType##WithParams:self.childManager.paramsForAPI success:^(EWResponse *response) {\
    [weakSelf cancellReqeustAnimation];\關(guān)閉動畫
[weakSelf requestSuccess:response];\
} fail:^(EWResponse *response) {\
    [weakSelf cancellReqeustAnimation];\關(guān)閉動畫
    [weakSelf requestFailed:response];\
}];\
}

在這里self.childManager.paramsForAPI就將APIManager中的參數(shù)傳遞過去了.至于原理,在于將作為EWBaseAPIManager的init方法重寫了,當(dāng)我們初始化子類TestAPIManager的時候,父類EWBaseAPIManager中的childManager就已經(jīng)成為了TestAPIManager:

- (instancetype)init
{
    self = [super init];
    if (self) {
        //初始化的時候,childManager即為當(dāng)前的子類,完成請求參數(shù)的傳遞
        if ([self conformsToProtocol:@protocol(EWAPIManagerProtocol)]) {
            self.childManager = (NSObject<EWAPIManagerProtocol> *)self;
        }
    }
    return self;
}

疑問4:底層如何進(jìn)行數(shù)據(jù)請求?
這里我設(shè)計了一個分發(fā)請求的類EWAPIRequest,里面目前只定義了三個方法:

/**
 *  get請求
 *
 *  @param params 傳入的參數(shù),必須包含url,請求類型,參數(shù),以及cache時間(沒有就填@0)
 *  @param success 請求成功后的回調(diào)(請將請求成功后想做的事情寫到這個block中)
 *  @param failure 請求失敗后的回調(diào)(請將請求失敗后想做的事情寫到這個block中)
 */
- (NSURLSessionDataTask *)sendRequestByGetWithParams:(NSDictionary *)params success:(void (^)(EWResponse *))success fail:(void (^)(EWResponse *))failure;
/**
 *  post請求
 *
 *  @param params 傳入的參數(shù),必須包含url,請求類型,參數(shù),以及cache時間(沒有就填@0)
 *  @param success 請求成功后的回調(diào)(請將請求成功后想做的事情寫到這個block中)
 *  @param failure 請求失敗后的回調(diào)(請將請求失敗后想做的事情寫到這個block中)
 */
- (NSURLSessionDataTask *)sendRequestByPostWithParams:(NSDictionary *)params success:(void (^)(EWResponse *))success fail:(void (^)(EWResponse *))failure;

/**
 *  圖片上傳
 *
 *  @param params 傳入的參數(shù),必須包含url,圖片內(nèi)容,圖片key為EWUploadImageKey
 *  @param success 請求成功后的回調(diào)(請將請求成功后想做的事情寫到這個block中)
 *  @param failure 請求失敗后的回調(diào)(請將請求失敗后想做的事情寫到這個block中)
 */
- (NSURLSessionDataTask *)sendRequestByPostImageWithParams:(NSDictionary *)params success:(void (^)(EWResponse *))success fail:(void (^)(EWResponse *))failure;

調(diào)用的地方在上方的請求宏中:

self.apiRequest sendRequestBy##requestType##WithParams:self.childManager.paramsForAPI success:^(EWResponse *response)

進(jìn)入到EWAPIRequest的方法實現(xiàn)中,我們可以看到里面是這樣實現(xiàn)的:

NSURLSessionDataTask *dataTask = nil;
    
    //通過工廠類獲得請求的實例,實例必須遵循這個請求的協(xié)議
    //這里采用硬編碼的方式,決定到底是用什么庫來進(jìn)行網(wǎng)絡(luò)請求,目的在于方便切換網(wǎng)絡(luò)庫
    //KEWRequestByAFN表示采用AFN這個網(wǎng)絡(luò)庫請求數(shù)據(jù)
    
    id<EWNetworkRequestProtocol> requestInstance = [[EWRequestInstanceFactory shareInstance] requestInstance:KEWRequestByAFN];
    
    //請求數(shù)據(jù)
    dataTask = [requestInstance requestByGetWithParams:params success:^(id responseObject) {
        
        //生成統(tǒng)一管理網(wǎng)絡(luò)數(shù)據(jù)的response
        //存入回調(diào)的數(shù)據(jù)
        EWResponse *response = [[EWResponse alloc] initWithResopnseObject:responseObject andError:nil];
        
        //回調(diào)這個response
        success ? success(response) : nil;
    } fail:^(NSError *error) {
        
        //存入錯誤信息
        EWResponse *errorResponse = [[EWResponse alloc] initWithResopnseObject:nil andError:error];
        
        //回調(diào)這個response
        failure ? failure(errorResponse) : nil;
        SLog(@"請求失敗-->%@",error);
    }];

這里涉及到了幾個協(xié)議和類,一一解釋一下
EWNetworkRequestProtocol:這個協(xié)議定義了最底層網(wǎng)絡(luò)請求庫需要遵守的方法,這里我用的AFN作為底層請求庫.
KEWRequestByAFN:這是個const常量,表示當(dāng)前的請求庫是基于AFN的
EWRequestInstanceFactory:這是個工廠類,為了返回遵守EWNetworkRequestProtocol協(xié)議的網(wǎng)絡(luò)庫的實例,底層是通過反射KEWRequestByAFN這個字符串獲得請求的實例,如果你要切換網(wǎng)絡(luò)庫,只需要新增一個請求類并且再定義一個const常量,在EWRequestInstanceFactory中替換KEWRequestByAFN即可.
EWResponse:這個類的作用是用來統(tǒng)一保存請求的數(shù)據(jù)和錯誤信息

拿到請求的實例requestInstance之后就調(diào)用EWNetworkRequestProtocol中的requestByGetWithParams方法來進(jìn)行網(wǎng)絡(luò)請求.之后就是將參數(shù)傳入AFN請求類中實現(xiàn)最終的請求,并回調(diào)結(jié)果.

疑問5:回調(diào)結(jié)果的處理?
EWAPIBaseManager中,我用了兩個私有方法在請求的宏里對回調(diào)結(jié)果進(jìn)行轉(zhuǎn)發(fā),然后將結(jié)果回調(diào)給EWAPICallBackProtocol協(xié)議中的方法:managerCallBackDidSuccess,managerCallBackDidFailed.

//回調(diào)成功的response,里面保存了請求成功的數(shù)據(jù)
- (void)requestSuccess:(EWResponse *)response{
//將response賦值給apiManager
    self.response = response;
    if ([self.delegate respondsToSelector:@selector(managerCallBackDidSuccess:)]) {
        [self.delegate managerCallBackDidSuccess:self];
    }
}
//回調(diào)失敗的response,里面保存了請求失敗的錯誤信息
- (void)requestFailed:(EWResponse *)response{
//將response賦值給apiManager
    self.response = response;
    if ([self.delegate respondsToSelector:@selector(managerCallBackDidFailed:)]) {
        [self.delegate managerCallBackDidFailed:self];
    }
}

就這樣,就實現(xiàn)了整個網(wǎng)絡(luò)請求的過程.至于這里為什么是回調(diào)response而不是直接回調(diào)responseObject,原因是用response可以統(tǒng)一管理回調(diào)數(shù)據(jù)和錯誤信息,就不需要再定義responseObject和error的變量了.

在這個庫里面我還添加了加載動畫,緩存,數(shù)據(jù)過濾等功能,有興趣可以自己研究下,demo在這里.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末隘击,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子驶冒,更是在濱河造成了極大的恐慌认罩,老刑警劉巖掐隐,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔚舀,死亡現(xiàn)場離奇詭異蟆盹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)巧娱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門碉怔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人禁添,你說我怎么就攤上這事撮胧。” “怎么了老翘?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵芹啥,是天一觀的道長。 經(jīng)常有香客問我铺峭,道長墓怀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任卫键,我火速辦了婚禮傀履,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘永罚。我一直安慰自己啤呼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布呢袱。 她就那樣靜靜地躺著,像睡著了一般翅敌。 火紅的嫁衣襯著肌膚如雪羞福。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天蚯涮,我揣著相機(jī)與錄音治专,去河邊找鬼。 笑死遭顶,一個胖子當(dāng)著我的面吹牛张峰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播棒旗,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼喘批,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起饶深,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤餐曹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后敌厘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體台猴,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年俱两,在試婚紗的時候發(fā)現(xiàn)自己被綠了饱狂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡宪彩,死狀恐怖嗡官,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情毯焕,我是刑警寧澤衍腥,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站纳猫,受9級特大地震影響婆咸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芜辕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一尚骄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侵续,春花似錦倔丈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至轧坎,卻和暖如春宏邮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缸血。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工蜜氨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捎泻。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓飒炎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親笆豁。 傳聞我的和親對象是個殘疾皇子郎汪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

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