網路層的封裝一直是專案中不足之處,前不久看了唐巧大神的YTKNetwork後又拜讀了宇大神的這篇部落格,前者讓我看到了離散型API封裝的典型例子,後者恰好又提供了用 protocol 封裝的很好思路以及說明了繼承方式的封裝的優缺點,於是結合兩者 LCNetwork 就誕生了。專案地址github,目前已經適配 AFNetworking 3.x
若遇到 Demo 閃退問題,請刪除 APP 重新執行,另外感謝zdoz提供免費的測試介面。
LCNetwork 主要功能有
-
支援
block
和delegate
的回撥方式 -
支援設定主、副兩個伺服器地址
-
支援
response
快取,基於TMCache -
支援統一的
argument
加工 -
支援統一的
response
加工 -
支援多個請求同時傳送,並統一設定它們的回撥
-
支援以類似於外掛的形式顯示HUD
-
支援獲取請求的實時進度
最終在 ViewController
中呼叫一個介面請求的例子如下
Api2 *api2 = [[Api2 alloc] init];
api2.requestArgument = @{
@"lat" : @"34.345",
@"lng" : @"113.678"
};
[api2 startWithCompletionBlockWithSuccess:^(Api2 *api2) {
self.weather2.text = api2.responseJSONObject[@"Weather"];
} failure:NULL];
整合
Cocoapods:
pod `LCNetwork`
使用
統一配置
LCNetworkConfig *config = [LCNetworkConfig sharedInstance];
config.mainBaseUrl = @"http://api.zdoz.net/";// 設定主伺服器地址
config.viceBaseUrl = @"https://api.zdoz.net/";// 設定副伺服器地址
建立介面呼叫類
每個請求都需要一個對應的類去執行,這樣的好處是介面所需要的資訊都整合到了這個API類的內部,不在暴露在Controller層。
建立一個API類需要繼承LCBaseRequest
類,並且遵守LCAPIRequest
協議,下面是最基本的API類的建立。
Api1.h
#import <LCNetwork/LCBaseRequest.h>
@interface Api1 : LCBaseRequest<LCAPIRequest>
@end
Api1.m
#import "Api1.h"
@implementation Api1
// 介面地址
- (NSString *)apiMethodName{
return @"getweather.aspx";
}
// 請求方式
- (LCRequestMethod)requestMethod{
return LCRequestMethodGet;
}
@end
- (NSString *)apiMethodName
和 - (LCRequestMethod)requestMethod
是 @required 方法,所以必須實現,這在一定程度上降低了因漏寫方法而crash的概率。
另外 @optional 方法提供瞭如下的功能
// 是否使用副Url
- (BOOL)useViceUrl;
// 是否快取資料 response 資料
- (BOOL)cacheResponse;
// 自定義超時時間
- (NSTimeInterval)requestTimeoutInterval;
// 用於 multipart 的資料block
- (AFConstructingBlock)constructingBodyBlock;
// response處理
- (id)responseProcess:(id)responseObject;
// 是否忽略統一的引數加工
- (BOOL)ignoreUnifiedResponseProcess;
// 返回完全自定義的介面地址
- (NSString *)customApiMethodName;
// 服務端資料接收型別,比如 LCRequestSerializerTypeJSON 用於 post json 資料
- (LCRequestSerializerType)requestSerializerType;
引數設定
請求的引數可以在外部設定,例如:
Api2 *api2 = [[Api2 alloc] init];
api2.requestArgument = @{
@"lat" : @"34.345",
@"lng" : @"113.678"
};
如果不想把引數的 key 值暴露在外部,也可以在 API 類中自定義初始化方法,例如:
Api2.h
@interface Api2 : LCBaseRequest<LCAPIRequest>
- (instancetype)initWith:(NSString *)lat lng:(NSString *)lng;
@end
Api2.m
#import "Api2.h"
@implementation Api2
- (instancetype)initWith:(NSString *)lat lng:(NSString *)lng{
self = [super init];
if (self) {
self.requestArgument = @{
@"lat" : lat,
@"lng" : lng
};
}
return self;
}
直接在初始化方法中使用 self.requestArgument = @{@"lat" : lat, lng" : lng}
其實不妥,原因請參考 唐巧 和 jymn_chen,如果想完全規避這樣的問題,請參考demo中的實現
統一處理argument
和 response
這裡需要用到另外一個協議 <LCProcessProtocol>
,比如我們的每個請求需要新增一個關於版本的引數:
LCProcessFilter.h
#import "LCNetworkConfig.h"
@interface LCProcessFilter : NSObject <LCProcessProtocol>
@end
LCProcessFilter.m
#import "LCBaseRequest.h"
@implementation LCProcessFilter
- (NSDictionary *) processArgumentWithRequest:(NSDictionary *)argument{
NSMutableDictionary *newParameters = [[NSMutableDictionary alloc] initWithDictionary:argument];
[newParameters setObject:@"1.0.0" forKey:@"version"];
return newParameters;
}
或者是處理類似於下面的 json 資料,服務端返回的資料結構都會帶result
和 ok
的 key
{
"result": {
"_id": "564a931dbbb03c7002a2c0f3",
"name": "clover",
"count": 0
},
"ok": true,
"message" : "成功"
}
那麼可以這樣處理
- (id) processResponseWithRequest:(id)response{
if ([response[@"ok"] boolValue]) {
return response[@"result"];
}
else{
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: response[@"message"]};
return [NSError errorWithDomain:ErrorDomain code:0 userInfo:userInfo];
}
}
也就是說當使用 api1.responseJSONObject
獲取資料時,返回的直接是 result
對應的值,或者是錯誤資訊。
最後,賦值給 LCNetworkConfig
的 processRule
LCProcessFilter *filter = [[LCProcessFilter alloc] init];
config.processRule = filter;
當然,如果你某個介面的 response 你不想做統一的處理,可以在請求子類中實現
- (BOOL)ignoreUnifiedResponseProcess{
return YES;
}
這樣返回的 response 就是原始資料
multipart/form-data
通常我們會用到上傳圖片或者其他檔案就需要用到 multipart/form-data
,同樣的只需要實現- (AFConstructingBlock)constructingBodyBlock;
協議方法即可,比如
- (AFConstructingBlock)constructingBodyBlock {
return ^(id<AFMultipartFormData> formData) {
NSData *data = UIImageJPEGRepresentation([UIImage imageNamed:@"currentPageDot"], 0.9);
NSString *name = @"image";
NSString *formKey = @"image";
NSString *type = @"image/jpeg";
[formData appendPartWithFileData:data name:formKey fileName:name mimeType:type];
};
}
對於多圖上傳可能還會需要知道進度情況,LCNetwork 在 1.1.0 版本之後提供了監聽進度的方法,只需要呼叫
- (void)startWithBlockProgress:(void (^)(NSProgress *progress))progress
success:(void (^)(id request))success
failure:(void (^)(id request))failure;
或者 - (void)requestProgress:(NSProgress *)progress
的協議方法,下面是一個具體例子:
MultiImageUploadApi *multiImageUploadApi = [[MultiImageUploadApi alloc] init];
multiImageUploadApi.images = @[[UIImage imageNamed:@"test"], [UIImage imageNamed:@"test1"]];
[multiImageUploadApi startWithBlockProgress:^(NSProgress *progress) {
NSLog(@"%f", progress.fractionCompleted);
} success:^(id request) {
} failure:NULL];
response 再加工
當類似於
{
"result": {
"_id": "564a931dbbb03c7002a2c0f3",
"name": "clover",
"count": 10
},
"ok": true,
"message" : "成功"
}
這樣的資料,如果已經對 response 做了統一的加工,比如成功後統一返回的資料是 result 中的資料,那麼返回的也是一個 NSDictionary
,可能無法滿足需求,這時候再把資料交給 LCBaseRequest
子類處理再合適不過了。比如需要直接獲取count
值,那麼只需要實現 - (id)responseProcess:(id)responseObject;
協議方法,具體如下
- (id)responseProcess:(id)responseObject{
return responseObject[@"count"];
}
__注意,不應該呼叫self.responseJSONObject
作為處理資料,請使用responseObject
來處理__,當實現這個協議方法後,使用 api1.responseJSONObject
獲取資料時,返回將是 count
的值。
關於HUD
如何顯示 “正在載入”的 HUD,請參考Demo中的 LCRequestAccessory
類