1. 程式人生 > >iOS 網路層文件

iOS 網路層文件

iOS 網路層架構說明

說明

iOS 的網路層使用 YTKNetwork作為網路層底層架構,在 YTKNetworkYTKRequest類和具體的業務請求層之間架設了一箇中間業務類LSBaseRequest,所有具體的 API 請求都繼承於此類。

YTKNetwork主要用於請求的傳送及回撥處理,YTKNetwork 的基本的思想是把每一個網路請求封裝成物件。使用 YTKNetwork,每一個請求都需要繼承 YTKRequest 類,通過覆蓋父類的一些方法來構造指定的網路請求。

這裡為了避免我們具體業務類的相關回調邏輯與 YTKNetwork 耦合過高,所以封裝了LSBaseRequest

來處理請求回撥的公共邏輯。

下面來一一說明。

LSBaseRequest 類的設計

iOS Network

繼承關係及中間層的 BaseRequest 的設計如上圖所示。

基本的原理和欄位說明:

首先一個請求的回撥都是直接一個 JSON 的字串,網路層的底層可以將這個字串轉化為NSDictionary型別的資料,這個是請求回撥的第一次處理得到的資料。處理的具體過程如下:

// 此處是在網路請求完成的回撥方法內拿到responseString的回撥字串的處理
NSData *data = [self.responseString dataUsingEncoding:NSUTF8StringEncoding];
if
(data) { NSMutableDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil]; // ... }else{ DDLogError(@"返回內容為空."); // ... }

所以,如果 API 的請求沒有特殊的要求,可能直接拿到這個 data資料來提供給 ViewController 使用。

但是,一般的請求並不會簡單的直接要求NSMutableDictionarydata資料,ViewController 希望能夠直接拿到其需要的資料型別。而展示資料一般也只需要兩種型別,分別是展示一個列表資料和展示一個實體模型的資料。所以業務請求 API 能夠自己處理回撥資料的“深加工”是View 和 ViewController希望看到的。因此,這裡直接給 BaseRequest

添加了兩個屬性,分別是泛型的回撥列表資料和泛型的實體模型資料,而NSDictionary型別的resultDic屬性是第一次處理的結果。

根據以上的需求,具體的業務請求的 API 都會具有三種附加的狀態屬性,分別是該請求是否需要登入,該請求是否需要一個列表的回撥資料,該請求是否需要一個實體模型的回撥資料。在初始化具體的 API 請求的時候就會對這三個屬性進行初始化。

具體的對應數屬性是:

@property (nonatomic, assign) BOOL isLogin;  // 是否需登入
@property (nonatomic, assign) BOOL isRetAry; // 是否需要返回陣列
@property (nonatomic, assign) BOOL isRetObj; // 是否需要返回實體 model

所以BaseRequest的對外暴露的方法也只有三個,此處甚至可以減少到兩個,即是所有的 API 請求自己處理自己的初始化,而不是像上面的使用中間類的初始化方法。

而 API 的初始化方法中,需要傳入該 API 請求需要的引數及其相關的狀態屬性。請求引數會根據請求的登入狀態屬性來決定是否新增AccessToken 引數

在具體的傳送 API 請求的時候,會根據以上三個狀態屬性來確定如何傳送請求,具體的處理邏輯如下:

- (void)startRequest{
    sendCount ++; // 請求重發的次數
    BOOL isOnline = [[LSUserDefaults sharedInstance] isOnLine];
    if (_isLogin && isOnline) {  // 需要登入且目前使用者已經登入
        [self start];
    }

    if(_isLogin && !isOnline) { // 需要登入但目前使用者並沒有登入
        self.failure(@{@"code" : NoLogin});
    }

    if (!_isLogin) {         // 不需要登入,直接登入
        [self start];
    }
}

請求完成之後的公共邏輯處理在requestCompleteFilterrequestFailedFilter回撥方法中實現,這兩個回撥方法是 YTKNetwork 中暴露出來給請求結果回撥處理使用的。

而一旦請求傳送成功,獲得回撥資料之後,回撥資料統一處理邏輯會根據 API 初始化攜帶的狀態來加工回撥資料,具體如下:

- (void)requestCompleteFilter{
    NSData *data = [self.responseString dataUsingEncoding:NSUTF8StringEncoding];
    if (data) {
          // 初次加工資料
        NSMutableDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
        NSEnumerator *enumerator = [dictionary keyEnumerator];
        NSString *key = @"code";
        int code = [dictionary[key] intValue];
        BOOL isExist = NO;
        while ((key = [enumerator nextObject])) {
            isExist = YES;
        }
        if (isExist) {
            switch (code) {
                case 200:
                    if (_isRetAry) { // 如果要求返回的是陣列,則在具體的 API 中再次深加工資料,並將加工之後的資料回撥
                        self.successArray(self.resultData);
                    }else if(_isRetObj) // 如果要求返回的是實體,則在具體的 API 中再次深加工資料,並將加工之後的資料回撥
                        self.successObject(self.model);
                    else // 如果要求返回沒有額外的要求,則在資料回撥中直接將初加工資料回撥
                        self.success(dictionary);
                    break;
                      // 請求成功但是獲取的不是預期的成功資料
                case 801:{
                    // 訪問令牌失效,重新獲取
                    [self requestTokenWithRetArray:_isRetAry retObj:_isRetObj];
                }
                    break;
                case 802:
                    [[PMStore store] removeUserDataAndLogOff];
                    self.failure(dictionary);
                  // 其他情況
            }
        }
        else{
            self.failure(@{@"msg" : @"服務暫時不可用,請稍後重試"});
        }
    }else{
          // 請求成功但是沒有獲取到資料
        DDLogWarn(@"self.success(nil)");
        self.failure(@{@"msg" : @"服務暫時不可用,請稍後重試"});;
    }

}

具體的深加工過程,在具體的 API 類中再做介紹。

請求傳送失敗的公共處理邏輯:

- (void)requestFailedFilter {
    NSData *data = [self.responseString dataUsingEncoding:NSUTF8StringEncoding];
    if (data) {
        NSMutableDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
        self.failure(dictionary);
    }else{
        DDLogError(@"返回內容為空.");
        self.failure(@{@"msg" : @"服務暫時不可用,請稍後重試"});
    }
}

此處在請求成功傳送並獲取回撥之後,會出現一種特殊的情況(上面 Code 為801 的情況):如果該請求需要登入狀態,即是會攜帶AccessToken來發送請求,但是請求的結果是服務端提示AccessToken過期,此時不能立即回撥該結果給 API 的呼叫者,而是要傳送一個LSServerTokenAPI請求來嘗試跟服務端請求換取新的AccessToken,如果服務端成功的返回了新的AccessToken,則應該讓該請求重新攜帶新的AccessToken重發請求資料。如果沒有獲取到新的AccessToken,則此時應該失敗回撥給呼叫者,告訴呼叫者該使用者已經掉線,需要重新登入才能繼續請求。
此處的具體處理邏輯程式碼如下:

- (void)requestTokenWithRetArray:(BOOL)isAry retObj:(BOOL)isObj{
    NSMutableDictionary *requrestDic = [NSMutableDictionary dictionaryWithDictionary:self.requestArgument];
    // accessToken 過期
    LSServerTokenAPI *accessAPI = [[LSServerTokenAPI alloc]init];
    [accessAPI startWithCompletionBlockWithSuccess:^(YTKBaseRequest *request) {   // 此處是直接使用的 YTKRequest 的回撥方法
        // you can use self here, retain cycle won't happen
        NSLog(@"succeed");
        NSData *data = [request.responseString dataUsingEncoding:NSUTF8StringEncoding];
        NSMutableDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
        NSDictionary *dic = (NSDictionary *)dictionary[@"data"][@"result"];
        HNUserModel *user = [HNUserModel mj_objectWithKeyValues:dic];
        if (user.accessToken) {   // 如果換取到新的 Token
            [[PMStore store]saveDataWithUserInfo:user];
            requrestDic[@"accessToken"] = user.accessToken;
            self.baseRequestArgument = requrestDic;
            if (sendCount > 3) {
                self.failure(@{@"msg" : @"重發系統錯誤,請稍後重試"});  // 重發最多三次,多餘三次的直接回調失敗
                return;
            }else{
               [self startRequest];  // 重發該請求
            }
        }else{
            [[PMStore store] removeUserDataAndLogOff];
            self.failure(@{@"code" : NoLogin});  // 沒有獲取到新的 Token,回撥資料登入失效
        }
    } failure:^(YTKBaseRequest *request) {
        // you can use self here, retain cycle won't happen
        NSLog(@"failed");
        NSData *data = [request.responseString dataUsingEncoding:NSUTF8StringEncoding];
        if (data) {
            NSMutableDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
            DDLogError(@"傳送請求更新 accessToken 失敗,返回內容:\n%@",dictionary);
            self.failure(dictionary);
        }else{
            self.failure(@{@"msg" : @"重新整理 Token 系統錯誤,請稍後重試"});
        }
    }];
}

這裡需要注意的是為了避免無限的迴圈呼叫,LSServerTokenAPI本身不是LSBaseRequest型別的,而是和LSBaseRequest一樣都是直接繼承自YTKRequest的,因此此處的LSServerTokenAPI請求的回撥不會再次經過LSBaseRequestrequestCompleteFilterrequestFailedFilter回撥方法中的公共邏輯,而是直接呼叫的startWithCompletionBlockWithSuccess:(YTKRequestCompletionBlock)success failure:(YTKRequestCompletionBlock)failure方法處理資料回撥資料。

operationAPI 類的設計

具體的業務類非常的簡單,因此非常多的回撥處理邏輯都在LSBaseRequest內部完成處理了。
如果需要建立一個新的業務請求 API,只要建立一個繼承自LSBaseRequest的 API 類,在類的標頭檔案宣告中,除特殊情況外,不需要做任何的處理,比如一個典型的請求個人資訊的 API 介面宣告如下:

@interface HNUserInfoAPI : LSBaseRequest
// 此處一般不需要宣告任何額外的屬性和方法
@end

而具體業務的 API 實現,也只需要配置該 API 的 URL,API 的請求方式即可。

額外的資料邏輯處理需求

如果該 API 請求對於回撥資料有特殊的加工需求:

  • 需要一個列表資料,則會增加下面這些處理邏輯,實現LSBaseRequestresultData的獲取方法 ,比如請求交易記錄的 API :
// 此處是實現LSBaseRequest的屬性
- (NSArray *)resultData{
    return [self handleData:self.resultDic];
}
// 對請求的回撥資料進行深加工,JSON 轉列表
- (NSArray *)handleData:(NSDictionary *)dictionary{
    NSMutableArray *resultArray = [NSMutableArray new];
    if ([dictionary count]) {
        NSDictionary *resultDic = (NSDictionary *)[[dictionary objectForKey:@"data"] objectForKey:@"result"];
        [resultArray addObjectsFromArray:[HNTradeRecordModel mj_objectArrayWithKeyValuesArray:resultDic]];
    }
    return resultArray;
}
  • 需要一個模型資料,則會增加下面這些處理邏輯, 實現LSBaseRequestmodel的獲取方法 ,比如請求個人資訊的 API :
- (id)model{
    return [self handleData:self.resultDic];
}
// 對請求的資料進行深加工,JSON 轉模型
- (HNUserModel *)handleData:(NSDictionary *)dictionary{
    if ([dictionary count]) {
        [[PMStore store]handleUserDictionary:dictionary resiterJpush:NO];
        NSDictionary *dic = [[dictionary objectForKey:@"data"] objectForKey:@"result"];
        HNUserModel *model = [HNUserModel mj_objectWithKeyValues:dic];
        return model;
    }else
        return NULL;
}
  • 如果沒有額外的資料需求,則上面這些邏輯都不需要。

具體業務流程說明:

如果需要新增一個新的業務請求類:
1. 新建一個新的基於LSBaseRequest
2. 完成 API 類的基本配置,包括 URL 和請求方式的配置
3. 如果 該 API 類有更多的資料處理需求,按照上面所寫的方式來處理

在 ViewController 或者 View 中初始化 API,一個例項如下:

- (void)loadDataFromService{
    // API 請求的引數
    NSMutableDictionary *dic = [[NSMutableDictionary alloc]initWithDictionary:@{@"offset":self.offset,
                                                                                @"limit":@"10"}];
      // 初始化 API 請求,傳入需要的引數及狀態資訊
    HNInquiryListAPI *request = [[HNInquiryListAPI alloc]initWithArgumentValueDictionary:dic isLogin:YES isRetAry:YES isRetObj:NO];
    [request setSuccessArray:^(NSArray *resultData) {
        // 初始化的要求 API 能夠對回撥資料進行深加工——返回列表,所以這裡直接設定列表的 block 回撥處理邏輯。
    }];
    [request setFailure:^(NSDictionary *dictionary) {
          // 請求失敗的回撥
    }];
    [request startRequest];  // 傳送請求
}

以上就是完整的網路層架構的說明及具體業務處理的流程。

其他說明——圖片及檔案上傳請求

上傳圖片的 API 不同於其他的業務處理 API,它不需要處理一堆的業務邏輯,而且目前也不需要登入等狀態,因此網路層將上傳圖片的 API 直接獨立了出來,直接繼承與YTKRequest。目前的圖片 API 如下,已經封裝完成,圖片微服務不做大的重構,此 API 也不需要做修改:

宣告:

#import <YTKRequest.h>

@interface HNUploadImageApi : YTKRequest

- (id)initWithImage:(NSArray *)image;  // 需要上傳的圖片檔案陣列

@property (nonatomic, strong) id baseRequestArgument;  // 請求的引數

@end

實現部分

#import "HNUploadImageApi.h"

@implementation HNUploadImageApi{
    NSArray *_image;
}

- (id)initWithImage:(NSArray *)image {
    self = [super init];
    if (self) {
        _image = image;  // 初始化圖片陣列的屬性
    } 
    return self;
}

- (YTKRequestMethod)requestMethod {
    return YTKRequestMethodPOST;
}

// 構建請求引數
- (void)setBaseRequestArgument:(id)baseRequestArgument{
    if (baseRequestArgument) {
        _baseRequestArgument = baseRequestArgument;
    }
}

- (id)requestArgument {
    if (_baseRequestArgument) {
        return _baseRequestArgument;
    }
    return nil;
}

- (NSString *)requestUrl {
    return UploadImageAPI;  // 上傳 URL 地址
}

// 請求的 body 的構造
- (AFConstructingBlock)constructingBodyBlock {
    return ^(id<AFMultipartFormData> formData) {
        for (int i = 0;i < _image.count; i++){
            NSData *data = UIImageJPEGRepresentation(_image[i], 0.5);
            if ((float)data.length/1024 > 1000) {
                data = UIImageJPEGRepresentation(_image[i], 1024*1000.0/(float)data.length);
            }
            NSString *name = [NSString stringWithFormat:@"image%d.png",i];
            NSString *type = @"image/jpeg";
            [formData appendPartWithFileData:data name:@"image" fileName:name mimeType:type];
        }
    };
}

@end

具體構建及傳送請求的流程如下,以上傳頭像為例:

HNUploadImageApi *api = [[HNUploadImageApi alloc] initWithImage:@[scaleImage]]; // 初始化請求,並傳遞需要上傳的圖片陣列
api.baseRequestArgument = @{@"type":@"0"};   // 構造請求的引數
[api startWithCompletionBlockWithSuccess:^(YTKBaseRequest *request){
    NSData *data = [request.responseString dataUsingEncoding:NSUTF8StringEncoding];
    if (data) {
        NSMutableDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
          // 初次處理上傳成功後的資料回撥
    }
} failure:^(YTKBaseRequest *request){
    // 上傳失敗後是回撥
}];