網路請求LCNetwork

bawn發表於2015-08-18

網路層的封裝一直是專案中不足之處,前不久看了唐巧大神的YTKNetwork後又拜讀了宇大神的這篇部落格,前者讓我看到了離散型API封裝的典型例子,後者恰好又提供了用 protocol 封裝的很好思路以及說明了繼承方式的封裝的優缺點,於是結合兩者 LCNetwork 就誕生了。專案地址github,目前已經適配 AFNetworking 3.x

若遇到 Demo 閃退問題,請刪除 APP 重新執行,另外感謝zdoz提供免費的測試介面。

LCNetwork 主要功能有

  1. 支援blockdelegate的回撥方式

  2. 支援設定主、副兩個伺服器地址

  3. 支援response快取,基於TMCache

  4. 支援統一的argument加工

  5. 支援統一的response加工

  6. 支援多個請求同時傳送,並統一設定它們的回撥

  7. 支援以類似於外掛的形式顯示HUD

  8. 支援獲取請求的實時進度

最終在 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中的實現

統一處理argumentresponse

這裡需要用到另外一個協議 <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 資料,服務端返回的資料結構都會帶resultok 的 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 對應的值,或者是錯誤資訊。

最後,賦值給 LCNetworkConfigprocessRule

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

相關文章