iOS重構-輕量級的網路請求封裝實踐
阿新 • • 發佈:2018-11-07
前言
在十分鐘搭建主流框架_簡單的網路部分(OC)
中,我們使用AFN框架順利的傳送網路請求並返回了有用資料,但對AFN框架的依賴十分嚴重,下面我們重構一下。
初步
- 很多時候,我們涉及到網路請求這塊,都離不開幾個第三方框架,
AFNetworking
,MJExtention
,MBProgressHUD(SV)
。 - 初學的時候,都會把它們寫到Controller裡面,如下:
[[AFHTTPSessionManager manager] GET:CYXRequestURL parameters:params success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { NSLog(@"請求成功"); // 利用MJExtension框架進行字典轉模型 weakSelf.menus = [CYXMenu objectArrayWithKeyValuesArray:responseObject[@"result"]]; // 重新整理資料(若不重新整理資料會顯示不出) [weakSelf.tableView reloadData]; } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) { NSLog(@"請求失敗 原因:%@",error); }];
-
這樣會造成耦合性過高的問題,靈活性也非常不好,因此,AFN的作者也推薦我們不要直接使用,新建一個網路請求類來繼承AFN的使用方式更好。
-
因此,繼承的方式,如下:
-
CYXHTTPSessionManager.h檔案
#import <AFHTTPSessionManager.h> @interface CYXHTTPSessionManager : AFHTTPSessionManager @end
-
CYXHTTPSessionManager.m檔案
#import "CYXHTTPSessionManager.h" @implementation CYXHTTPSessionManager + (instancetype)manager{ CYXHTTPSessionManager *mgr = [super manager]; // 這裡可以做一些統一的配置 // mgr.responseSerializer = ; // mgr.requestSerializer = ; return mgr; } @end
-
-
呼叫方式:
/** 請求管理者 */ @property (nonatomic,weak) CYXHTTPSessionManager * manager; // 傳送請求 [self.manager GET:CYXRequestURL parameters:params success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { // 儲存 maxtime weakSelf.maxtime = responseObject[@"info"][@"maxtime"]; weakSelf.topics = [CYXTopic objectArrayWithKeyValuesArray:responseObject[@"list"]]; CYXLog(@"%@",responseObject[@"list"]); [weakSelf.tableView reloadData]; // 結束重新整理 [weakSelf.tableView.header endRefreshing]; } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) { [weakSelf.tableView.header endRefreshing]; }];
- 這樣,已經降低了一點耦合度,也不需要在每個需要傳送網路請求的Controller中引入AFN框架了。但對於MJExtension框架的依賴還是沒有改善。
進階
-
通過觀察,我們發現其實大部分的GET和POST請求的前幾步基本使用步驟是大致相同的,相同的步驟如下:
- 1.通過AFN請求回來JSON資料
- 2.通過JSON資料,取出需要使用的字典陣列/字典
- 3.使用字典轉模型框架(MJExtension)把字典陣列轉化為模型陣列/字典轉化為模型
-
因此,我們思考能不能把這些相同的步驟封裝起來,以後就不需要重複寫這些程式碼了,我們都知道一條經典的程式設計法則:“Don't repeat youself”。這就是我們封裝與重構的理由!
1.基層請求的封裝
- 本文示例封裝POST請求
- CYXHttpRequest.h檔案
#import <Foundation/Foundation.h>
#import "AFNetworking.h"
@interface CYXHttpRequest : NSObject
/**
* 傳送一個POST請求
*
* @param url 請求路徑
* @param params 請求引數
* @param success 請求成功後的回撥
* @param failure 請求失敗後的回撥
*/
+ (void)post:(NSString *)url params:(NSDictionary *)params success:(void (^)(id responseObj))success failure:(void (^)(NSError *error))failure;
@end
- CYXHttpRequest.m檔案
#import "CYXHttpRequest.h"
@implementation CYXHttpRequest
+ (void)post:(NSString *)url params:(NSDictionary *)params success:(void (^)(id))success failure:(void (^)(NSError *))failure
{
// 1.獲得請求管理者
AFHTTPSessionManager *mgr = [AFHTTPSessionManager manager];
// 2.申明返回的結果是text/html型別
mgr.responseSerializer = [AFHTTPResponseSerializer serializer];
// 3.設定超時時間為10s
mgr.requestSerializer.timeoutInterval = 10;
// 4.傳送POST請求
[mgr POST:url parameters:params progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (success) {
success(responseObject);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (failure) {
failure(error);
}
}];
}
@end
- 現在已經可以把網路資料請求回來了,輪到第二個步驟了:觀察請求回來的JSON資料,取出需要使用的字典陣列/字典。在這裡再作一層封裝。舉個簡單的例子,假如返回的JSON資料結構如下:
{
"error_code": 0,
"reason": "Success",
"result": [{
"id": 370622,
"title": "西紅柿蒜薹炒雞蛋",
"tags": "廚房用具;廚具;加工工藝;基本工藝;菜品;菜餚;家常菜;炒;炒鍋;熱菜;防輻射;開胃;蔬菜類;果實類;蒜薹;西紅柿;禽蛋類;蛋;雞蛋;",
"intro": "我這的蒜薹雞蛋都愛加西紅柿、辣椒一起炒的,這是習慣所致,愛吃西紅柿,愛吃辣椒,還愛把菜搭配的顏色亮麗,當然味道也不差。",
"ingredients": "西紅柿:1個;蒜薹:200g;雞蛋:2個;",
"burden": "油:適量;鹽:適量;青辣椒:1個;紅辣椒:1個;",
"albums": "http://imgs.haoservice.com/CaiPu/pic/recipe/l/be/a7/370622_86e12b.jpg",
}
{
"id": 433079,
"title": "西紅柿酸奶",
"tags": "促進食慾;減肥;懶人食譜;消暑食譜;美容養顏;",
"intro": "新疆人愛吃西紅柿那是有目共睹的,菜裡面加西紅柿的數不勝數,就連舌尖2在吐魯番拍的葡萄乾抓飯裡面都加西紅柿。",
"ingredients": "酸奶:400g;西紅柿:200g;",
"burden": "白糖:20g;",
"albums": "http://imgs.haoservice.com/CaiPu/pic/recipe/l/b7/9b/433079_377373.jpg",
}
{···}]
}
2.簡單業務邏輯封裝
- 現在只需要使用到
result
資料(並對應CYXMenu
模型),在公司中,介面一般會有比較好的規範,即每個介面的模型屬性一般都有統一的命名。 - 我們使用時,通常會把
result
字典陣列轉化成CYXMenu
模型陣列。因此,可以進一步的封裝出CYXBaseRequest
物件。 CYXBaseRequest
類實現思路如下:- 1.使用
CYXHttpRequest
發起網路請求,返回資料中取到result
- 2.使用
MJExtension
將result
字典陣列轉化成CYXMenu
模型陣列,並返回模型陣列 - 3.外界只需要傳遞進來一個
resultClass
即可。
- 1.使用
- CYXBaseRequest實現程式碼如下:
- CYXBaseRequest.h檔案
#import <Foundation/Foundation.h>
@interface CYXBaseRequest : NSObject
/**
* 返回result 資料模型
*
* @param url 請求地址
* @param param 請求引數
* @param resultClass 需要轉換返回的資料模型
* @param success 請求成功後的回撥
* @param warn 請求失敗後警告提示語
* @param failure 請求失敗後的回撥
* @param tokenInvalid token過期後的回撥
*/
+ (void)postResultWithUrl:(NSString *)url param:(id)param
resultClass:(Class)resultClass
success:(void (^)(id result))success
warn:(void (^)(NSString *warnMsg))warn
failure:(void (^)(NSError *error))failure
tokenInvalid:(void (^)())tokenInvalid;
/**
* 返回result 資料模型(帶HUD)
*
* @param url 請求地址
* @param param 請求引數
* @param resultClass 需要轉換返回的資料模型
* @param success 請求成功後的回撥
* @param warn 請求失敗後警告提示語
* @param failure 請求失敗後的回撥
* @param tokenInvalid token過期後的回撥
*/
+ (void)postResultHUDWithUrl:(NSString *)url param:(id)param
resultClass:(Class)resultClass
success:(void (^)(id result))success
warn:(void (^)(NSString *warnMsg))warn
failure:(void (^)(NSError *error))failure
tokenInvalid:(void (^)())tokenInvalid;
/**
* 組合請求引數
*
* @param dict 外部引數字典
*
* @return 返回組合引數
*/
+ (NSMutableDictionary *)requestParams:(NSDictionary *)dict;
@end
- CYXBaseRequest.m檔案
#import "CYXBaseRequest.h"
#import "CYXHttpRequest.h"
#import "ExceptionMsgTips.h"
#import "MJExtension.h"
@implementation BSBaseRequest
/**
* 返回result 資料模型(HUD)
*/
+ (void)postResultHUDWithUrl:(NSString *)url param:(id)param
resultClass:(Class)resultClass
success:(void (^)(id result))success
warn:(void (^)(NSString *warnMsg))warn
failure:(void (^)(NSError *error))failure
tokenInvalid:(void (^)())tokenInvalid
{
[self postBaseHUDWithUrl:url param:param resultClass:resultClass
success:^(id responseObj) {
if (!resultClass) {
success(nil);
return;
}
success([resultClass mj_objectArrayWithKeyValuesArray:responseObj[@"result"]]);
}
warn:warn
failure:failure
tokenInvalid:tokenInvalid];
}
/**
* 返回result 資料模型
*/
+ (void)postResultWithUrl:(NSString *)url param:(id)param
resultClass:(Class)resultClass
success:(void (^)(id result))success
warn:(void (^)(NSString *warnMsg))warn
failure:(void (^)(NSError *error))failure
tokenInvalid:(void (^)())tokenInvalid
{
[self postBaseWithUrl:url param:param resultClass:resultClass
success:^(id responseObj) {
if (!resultClass) {
success(nil);
return;
}
success([resultClass mj_objectArrayWithKeyValuesArray:responseObj[@"result"]]);
}
warn:warn
failure:failure
tokenInvalid:tokenInvalid];
}
/**
* 資料模型基類方法
*/
+ (void)postBaseWithUrl:(NSString *)url param:(id)param
resultClass:(Class)resultClass
success:(void (^)(id result))success
warn:(void (^)(NSString *warnMsg))warn
failure:(void (^)(NSError *error))failure
tokenInvalid:(void (^)())tokenInvalid
{
// url = [NSString stringWithFormat:@"%@%@",Host,url];
CYXLog(@"\\n請求連結地址---> %@",url);
//狀態列菊花
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[CYXHttpRequest post:url params:param success:^(id responseObj) {
if (success) {
NSDictionary *dictData = [NSJSONSerialization JSONObjectWithData:responseObj options:kNilOptions error:nil];
CYXLog(@"請求成功,返回資料 : %@",dictData);
success(dictData);
}
//狀態列菊花
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
} failure:^(NSError *error) {
if (failure) {
failure(error);
CYXLog(@"請求失敗:%@",error);
}
//狀態列菊花
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}];
}
/**
* 資料模型基類(帶HUD)
*/
+ (void)postBaseHUDWithUrl:(NSString *)url param:(id)param
resultClass:(Class)resultClass
success:(void (^)(id result))success
warn:(void (^)(NSString *warnMsg))warn
failure:(void (^)(NSError *error))failure
tokenInvalid:(void (^)())tokenInvalid
{
[SVProgressHUD showWithStatus:@""];
[self postBaseWithUrl:url param:param resultClass:resultClass success:^(id responseObj) {
[SVProgressHUD dismiss]; //隱藏loading
success(responseObj);
} warn:^(NSString *warnMsg) {
[SVProgressHUD dismiss];
warn(warnMsg);
} failure:^(NSError *fail) {
[SVProgressHUD dismiss];
failure(fail);
} tokenInvalid:^{
[SVProgressHUD dismiss];
tokenInvalid();
}];
}
@end
- 到這裡,輕量級的封裝介紹已經全部介紹完了,更多的功能封裝有待讀者自己去研究了。既然封裝好了,下面我們來介紹一下如何使用,其實非常簡單。
使用介紹
- 1.把上述兩個類的.h .m 文化拖到您專案中,最好新建一個<Request>資料夾。
- 2.在需要傳送請求的Controller中
#import "CYXBaseRequest.h"
- 3.傳送請求方法中的程式碼如下:
- (使用CYXBaseRequest):
#pragma mark - 請求資料方法
- (void)loadData{
self.pn = 1;
// 請求引數(根據介面文件編寫)
NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[@"menu"] = @"西紅柿";
params[@"pn"] = @(self.pn);
params[@"rn"] = @"10";
params[@"key"] = @"fcfdb87c50c1485e9e7fa9f839c4b1a8";
[CYXBaseRequest postResultWithUrl:CYXRequestURL param:params resultClass:[CYXMenu class] success:^(id result) {
CYXLog(@"請求成功,返回資料 : %@",result);
self.menus = result;
self.pn ++;
// 重新整理資料(若不重新整理資料會顯示不出)
[self.tableView reloadData];
[self.tableView.mj_header endRefreshing];
} warn:^(NSString *warnMsg) {
} failure:^(NSError *error) {
CYXLog(@"請求失敗 原因:%@",error);
[self.tableView.mj_header endRefreshing];
} tokenInvalid:^{
// 有登入操作的業務,這裡返回登入狀態
}];
}
- 在這裡對比一下不使用CYXBaseRequest的傳送請求方法程式碼:
#pragma mark - 請求資料方法
- (void)loadData{
self.pn = 1;
// 請求引數(根據介面文件編寫)
NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[@"menu"] = @"西紅柿";
params[@"pn"] = @(self.pn);
params[@"rn"] = @"10";
params[@"key"] = @"fcfdb87c50c1485e9e7fa9f839c4b1a8";
[self.manager.tasks makeObjectsPerformSelector:@selector(cancel)];
[self.manager.responseSerializer setAcceptableContentTypes:[NSSet setWithObject:@"text/html"]];
[self.manager POST:CYXRequestURL parameters:params progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
CYXLog(@"請求成功,返回資料 : %@",responseObject);
// 利用MJExtension框架進行字典轉模型
weakSelf.menus = [CYXMenu mj_objectArrayWithKeyValuesArray:responseObject[@"result"]];
weakSelf.pn ++;
// 重新整理資料(若不重新整理資料會顯示不出)
[weakSelf.tableView reloadData];
[weakSelf.tableView.mj_header endRefreshing];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
CYXLog(@"請求失敗 原因:%@",error);
[weakSelf.tableView.mj_header endRefreshing];
}];
}
- 雖然從程式碼看似兩種使用差別不太大(只是少了幾行程式碼),但相比之下,前者確實降低了對AFN等框架的依賴,並省去了每次都手動轉一下模型的煩惱,現在你只需要把
resultClass
傳過去,返回的資料便是已經轉化好的模型,並在CYXBaseRequest
內打印出請求連結地址
,返回資料
等有用資訊,方便除錯,介面設計也類似AFN,使用簡便。
- TIPS:建議使用者可以在每個模組都建立Request檔案(繼承CYXBaseRequest),統一進行網路請求,這樣更方便管理。
注:
- 本封裝實踐只對網路請求進行初步的簡單封裝,僅適用於中小型的專案,並不涉及快取、校驗等高階功能,如果有高階需求,建議研究下猿題庫的YTKNetwork網路庫。
作者:Developer_Yancy
連結:https://www.jianshu.com/p/d4ff9fd0dcba
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。