關(guān)于網(wǎng)絡(luò)層的設(shè)計(一)——和業(yè)務(wù)層的對接

前言

關(guān)于網(wǎng)絡(luò)層的設(shè)計,最主要的是和業(yè)務(wù)層的對接問題杖们。
網(wǎng)絡(luò)層設(shè)計得好悉抵,可以讓業(yè)務(wù)層開發(fā)事半功倍肩狂;反之摘完,若網(wǎng)絡(luò)層設(shè)計地很糟糕,則會讓業(yè)務(wù)層開發(fā)事倍功半傻谁,心里法克連連孝治。


p1.jpeg

關(guān)于網(wǎng)絡(luò)層和業(yè)務(wù)層的對接,我們一般從下面幾個方面進行考量:

  • 選擇哪種方式請求網(wǎng)絡(luò)數(shù)據(jù)审磁,系統(tǒng)自帶的還是AFNetworking谈飒?
  • 以什么模式給業(yè)務(wù)層交付數(shù)據(jù)?delegate 還是block态蒂?
  • 交付給業(yè)務(wù)層什么形式的數(shù)據(jù)杭措?直接返回dict就行了,還是把dict在網(wǎng)絡(luò)層轉(zhuǎn)換為ResultModel再交給業(yè)務(wù)層钾恢?
  • 封裝API應(yīng)該選擇集約型還是離散型手素?

第一個問題,“選擇哪種方式請求網(wǎng)絡(luò)數(shù)據(jù)瘩蚪?”泉懦。第三方庫AFNetworking很強大而且使用起來比較簡單,所以一般我們選擇AFNetworking疹瘦。蘋果自帶的NSURLSession等以后再研究崩哩。

第二個問題,“以什么模式給業(yè)務(wù)層交付數(shù)據(jù)言沐?”邓嘹。一般選擇Delegate和block。關(guān)于它們险胰,應(yīng)該說各有利弊吧汹押,具體使用場景具體選擇使用。block使用起來較方便鸯乃,但也有調(diào)試時不好追蹤鲸阻,容易出現(xiàn)循環(huán)引用等坑的缺點跋涣。而且若在業(yè)務(wù)層block返回數(shù)據(jù)后,要做比較復(fù)雜的邏輯處理的話鸟悴,那在block里會寫有大段代碼陈辱,這樣閱讀起來也不好,使代碼整體結(jié)構(gòu)顯得很不清晰细诸。
但是沛贪,在此,我們?nèi)韵纫詁lock為例來理解網(wǎng)絡(luò)層的設(shè)計震贵。

第三個問題利赋,“交付給業(yè)務(wù)層什么形式的數(shù)據(jù)?”猩系。我們設(shè)計網(wǎng)絡(luò)層媚送,就要想著能盡量減輕業(yè)務(wù)層的開發(fā)量,最好把網(wǎng)絡(luò)層從后臺拿到的一大串?dāng)?shù)據(jù)寇甸,剝離塘偎、加工、整理成業(yè)務(wù)層需要的數(shù)據(jù)格式然后再交付給它拿霉。

第四個問題吟秩,“封裝API應(yīng)該選擇集約型還是離散型?”绽淘。所謂集約型涵防,就是只能業(yè)務(wù)層提供一個方法,所有業(yè)務(wù)層的網(wǎng)絡(luò)請求都要通過該方法完成沪铭。因此壮池,該方法至少要能傳入接口路徑(path)、請求方式(get/post)伦意、請求參數(shù)(param)等火窒。集約型的好處是對于網(wǎng)絡(luò)層的編寫來說方便快捷,但對業(yè)務(wù)層來說要傳入這么多參數(shù)并不太好驮肉。我們設(shè)計的目的就是盡量使業(yè)務(wù)層使用起來簡單輕巧熏矿,所以我們常常采用離散型方式。(說得不太恰當(dāng)离钝。集約型為所有的業(yè)務(wù)請求提供一個接口票编,省去了編寫業(yè)務(wù)模塊xxxManager的工作量。但對集約型而言卵渴,提供的這個唯一的網(wǎng)絡(luò)請求方法得有接口地址慧域,請求方式,接口參數(shù)等多個參數(shù)浪读。這是其繁瑣之處昔榴,而離散型則為了避免給業(yè)務(wù)層帶來這樣的繁瑣辛藻,而在xxxManager提供的接口方法里自己配置了接口地址interface和請求方式,并以方法名加以體現(xiàn)互订。那對業(yè)務(wù)層開發(fā)來說就簡潔明了了許多吱肌。一個比如用戶模塊UserManager里的對登錄請求的封裝,只需業(yè)務(wù)層傳入accountpassword兩個參數(shù)仰禽,而接口地址和請求方式已封裝在其方法里了login:password:success:failure氮墨,而且方法名也體現(xiàn)出了請求接口login。但離散型的問題是無疑為增加代碼量吐葵,為編寫xxxManager層將花費大量時間规揪。)
不言而喻,和集約型相對的温峭,離散型就是根據(jù)功能模塊分為不同的模塊猛铅,分別提供不同的方法給業(yè)務(wù)層調(diào)用。比如诚镰,把和用戶有關(guān)的所有網(wǎng)絡(luò)請求奕坟,放在一個叫UserManager的類中,登錄清笨、注冊、修改密碼等分別提供不同的方法刃跛,這樣的好處在于抠艾,一、不同功能模塊放在不同的文件中桨昙,使項目結(jié)構(gòu)更清晰检号,維護升級更容易;二蛙酪、對于業(yè)務(wù)開發(fā)人員來說齐苛,不同的功能叫不同的方法名這樣更友好易懂。三桂塞、更重要的是凹蜂,你可以在xxxManager這一層做一些針對該模塊的個性化處理。沒錯阁危,你可以在這一層完成上個問題中所說的數(shù)據(jù)加工后再交付給業(yè)務(wù)層玛痊。我們把和用戶相關(guān)的網(wǎng)絡(luò)請求API都定義在UserManager類中,并在其中轉(zhuǎn)換為UserObject然后交付給業(yè)務(wù)層狂打。
除此外擂煞,“離散”不僅體現(xiàn)在提供的API方法上,還體現(xiàn)在網(wǎng)絡(luò)請求連接上趴乡。我們定義一個HttpClient類对省,在該類中專門完成對服務(wù)器的網(wǎng)絡(luò)請求蝗拿。并且給xxxManager這一層提供不同請求方式對應(yīng)的方法。

好了蒿涎,基本結(jié)構(gòu)就是這樣蛹磺,下面上代碼。我們“從內(nèi)至外”的看代碼同仆。
首先就是HttpClient這個類了萤捆,該類是完成網(wǎng)絡(luò)請求連接的核心。并給xxxManager提供網(wǎng)絡(luò)連接的接口方法俗批。

HttpClient.h

#import <Foundation/Foundation.h>
#import "AFNetworking.h"

#define BaseURL @"http://192.168.1.125/v1/" // 服務(wù)器地址

typedef NS_ENUM(NSInteger, RequestMethod)
{
    POST = 0,
    GET,
    PUT,
    DELETE,
};


@interface HttpClient : NSObject

// get請求
- (void)getOfPath:(NSString *)path
          prama:(id)prama
        success:(void(^)(id result))success
        failure:(void(^)(NSError *error))failure;


// post請求
- (void)postOfPath:(NSString *)path
          prama:(id)prama
        success:(void(^)(id result))success
        failure:(void(^)(NSError *error))failure;

@end

HttpClient.m
注意俗或,我們提供給外部get和post請求對應(yīng)的兩個方法調(diào)用,但其實在內(nèi)部岁忘,我們是定義了一個“全能方法”來完成網(wǎng)絡(luò)連接的辛慰,這才是核心。

#import "HttpClient.h"

@implementation HttpClient

// get
- (void)getOfPath:(NSString *)path
            prama:(id)prama
          success:(void(^)(id result))success
          failure:(void(^)(NSError *error))failure
{
    [self sendRequestOfType:GET path:path prama:prama success:success failure:failure];
}

// post
- (void)postOfPath:(NSString *)path
             prama:(id)prama
           success:(void(^)(id result))success
           failure:(void(^)(NSError *error))failure
{
    [self sendRequestOfType:POST path:path prama:prama success:success failure:failure];
}



// 完成網(wǎng)絡(luò)連接的核心
- (void)sendRequestOfType:(RequestMethod)requestType
                     path:(NSString *)path
                    prama:(id)prama
                  success:(void(^)(id result))success
                  failure:(void(^)(NSError *error))failure
{
    // 拼接參數(shù)干像,得到完整的接口地址
    NSURL *baseUrl = [NSURL URLWithString:BaseURL];
    NSString *pathUrl = [NSString stringWithFormat:@"%@%@",BaseURL,path];
    
    /**
     通常在此帅腌,可以完成拼接公共參數(shù)、密碼加密麻汰、或者簽名認證等操作速客。
     */

    AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseUrl];
    
    switch (requestType)
    {
            
// ------------ GET -------------
        case GET:
        {
            [manager GET:pathUrl
              parameters:prama
                 success:^(AFHTTPRequestOperation *operation, id responseObject) {
                        success(responseObject);
            } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                        failure(error);
            }];
            break;
        }
            
// ------------ POST -------------
            
        case POST:
        {
            [manager POST:pathUrl
               parameters:prama success:^(AFHTTPRequestOperation *operation, id responseObject) {
                        success(responseObject);
            } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                        failure(error);
            }];
            break;
        }
            
        default:
            break;
    }
    
}

@end

好了,現(xiàn)在看看xxxManager層五鲫。
UserManager.h

#import <Foundation/Foundation.h>
#import "UserObject.h"

@interface UserManager : NSObject

// 提供獲取UserManager實例的類方法
+ (UserManager *)getInstance;

// 給業(yè)務(wù)層提供的“登錄”功能的網(wǎng)絡(luò)數(shù)據(jù)請求方法
- (void)login:(NSString *)account
     password:(NSString *)password
      success:(void(^)(UserObject *userObj))success
      failure:(void(^)(NSError *error))failure;

@end

UserManager.m
登錄溺职、注冊、修改密碼位喂、修改個人資料分別提供不同的方法浪耘。在相應(yīng)方法里通過調(diào)用HttpClient提供的網(wǎng)絡(luò)連接方法完成網(wǎng)絡(luò)連接,然后把數(shù)據(jù)轉(zhuǎn)換加工成業(yè)務(wù)層需要的數(shù)據(jù)格式UserObject塑崖,再交付之七冲。

#import "UserManager.h"
#import "HttpClient.h"
#include "MJExtension.h"


@implementation UserManager


//=================================== UserManager ==========================================//
// 和User有關(guān)的所有請求接口路徑
NSString *const kUserLogin              = @"user/login";
NSString *const kUserRegister           = @"user/register";


+ (UserManager *)getInstance
{
    return [UserManager new];
}

- (void)login:(NSString *)account
     password:(NSString *)password
      success:(void(^)(UserObject *userObj))success
      failure:(void(^)(NSError *error))failure
{
    NSDictionary *pramaDict = @{@"account":account, @"password":password};

    // 通過HttpClient提供的請求方法完成網(wǎng)絡(luò)請求
    HttpClient *httpClient = [HttpClient new];
    
    [httpClient postOfPath:kUserLogin prama:pramaDict success:^(id result) {
        // 把服務(wù)器返回的json數(shù)據(jù)result轉(zhuǎn)換為UserObject類型的userObj
        UserObject *userObj = [UserObject mj_objectWithKeyValues:result];
        success(userObj);
    } failure:^(NSError *error) {
        failure(error);
    }];
}

@end

好了。當(dāng)業(yè)務(wù)層開發(fā)人員需要完成“登錄”功能時规婆,只需調(diào)用UserManager中我們定義的login方法就得到了網(wǎng)絡(luò)數(shù)據(jù)澜躺,并且已轉(zhuǎn)為UserObject給我們。

#import "ViewController.h"
#import "UserManager.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [[UserManager getInstance] login:@"wang66" password:@"123456" success:^(UserObject *userObj) {
        NSLog(@"登錄后后臺返回用戶信息----%@",userObj.description);
    } failure:^(NSError *error) {
        NSLog(@"登錄失敗----%@",error);
    }];
    
    
}

@end



補充和優(yōu)化

上面我們實現(xiàn)了一個簡單的網(wǎng)絡(luò)層聋呢,但其實是比較簡陋的苗踪。真是情況要考慮很多地方的。

1. 在請求中添加簽名認證削锰,保證請求來源于我們自己的APP通铲。

2. 取消無用的請求。
比如器贩,比如我們剛進入一個界面后颅夺,此刻便會發(fā)出一條該界面數(shù)據(jù)的請求朋截,但是此時用戶卻點了“返回”,退回了上個界面吧黄。此時上個界面的請求已經(jīng)飛出但還未完成部服。這時,我們應(yīng)當(dāng)取消上個界面的請求拗慨,釋放帶寬廓八。這樣對于下來的網(wǎng)絡(luò)請求是有利的。

3. 錯誤信息的處理

// ------------ GET -------------
        case GET:
        {
            [manager GET:pathUrl
              parameters:prama
                 success:^(AFHTTPRequestOperation *operation, id responseObject) {
                        success(responseObject);
            } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                        failure(error);
            }];
            break;
        }

這段代碼赵抢,AFNetworking提供的GET請求剧蹂,請求成功時回調(diào)block返回responseObject,失敗時返回error烦却。但是請注意宠叼,這里的錯誤回調(diào)僅僅指網(wǎng)絡(luò)請求錯誤,要注意區(qū)分網(wǎng)絡(luò)錯誤和業(yè)務(wù)錯誤(也就是網(wǎng)絡(luò)請求是成功的其爵,但是對于我們的業(yè)務(wù)來說冒冬,是有問題的)。這些信息同樣是會在responseObject返回摩渺。實際上一般網(wǎng)絡(luò)請求成功后简烤,后臺返回的responseObject一般都有errorCode字段,只有當(dāng)errorCode=0時证逻,就說明一切OK乐埠,正常返回了我們需要的數(shù)據(jù)。所以囚企,為了給業(yè)務(wù)層提供方便,我們還得在網(wǎng)絡(luò)層做些處理瑞眼。使交付給業(yè)務(wù)層的數(shù)據(jù)里龙宏,成功回調(diào)的block里就純粹了業(yè)務(wù)邏輯意義上正確的數(shù)據(jù),而失敗的回調(diào)里的數(shù)據(jù)則包括一切錯誤信息伤疙。所說的處理就是在該方法里對回調(diào)block做層包裝银酗。

// 完成網(wǎng)絡(luò)連接的核心
- (void)sendRequestOfType:(RequestMethod)requestType
                     path:(NSString *)path
                    prama:(id)prama
                  success:(void(^)(id result))success
                  failure:(void(^)(NSError *error))failure
{
    // 拼接參數(shù),得到完整的接口地址
    NSURL *baseUrl = [NSURL URLWithString:BaseURL];
    NSString *pathUrl = [NSString stringWithFormat:@"%@%@",BaseURL,path];
    
    /**
     通常在此徒像,可以拼接一些公共參數(shù)黍特,或者簽名認證參數(shù)。
     */

    AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseUrl];
    
    // ------------------ 包裝回調(diào)block ------------------
    //請求成功block
    void(^ok)(id responseObject) = ^(id responseObject){
        if([responseObject isKindOfClass:[NSDictionary class]])
        {
            Result *result = [Result mj_objectWithKeyValues:responseObject];
            if (result.errorCode == 0)
            {
                success(responseObject); //業(yè)務(wù)邏輯意義上的正確返回锯蛀。
            }
            else
            {
                // 有錯誤灭衷。
                NSError *error = [NSError errorWithDomain:result.message code:result.errorCode userInfo:nil];
                failure(error);
            }
        }
        else
        {
            NSError *error = [NSError errorWithDomain:@"服務(wù)返回數(shù)據(jù)異常" code:-1 userInfo:nil];
            failure(error);
        }
    };
    
    //請求失敗block
    void(^fail)(NSError *error) = ^(NSError *error){
        failure(error);
    };
    
    // ------------------------------------


    
    switch (requestType)
    {
// ------------ GET -------------
        case GET:
        {
            [manager GET:pathUrl
              parameters:prama
                 success:^(AFHTTPRequestOperation *operation, id responseObject) {
//                        success(responseObject);
                     ok(responseObject);
            } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//                        failure(error);
                    fail(error);
                
            }];
            break;
        }
            
// ------------ POST -------------
            
        case POST:
        {
            [manager POST:pathUrl
               parameters:prama success:^(AFHTTPRequestOperation *operation, id responseObject) {
//                        success(responseObject);
                   ok(responseObject);
            } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//                        failure(error);
                    fail(error);
            }];
            break;
        }
            
        default:
            break;
    }
}

** 4.多服務(wù)器多環(huán)境切換:**
一般比較規(guī)范的項目都有開發(fā)環(huán)境、測試環(huán)境旁涤、預(yù)發(fā)布環(huán)境翔曲、正式環(huán)境(生產(chǎn)環(huán)境)四種環(huán)境迫像,它們對應(yīng)的服務(wù)器地址分別是不同的。在項目版本迭代過程以“開發(fā)——>測試——>預(yù)發(fā)布——>正式”這個順序進行的瞳遍。開發(fā)環(huán)境就是新需求下來后的更改闻妓。測試環(huán)境就是給測試打了包后,改bug時的更改掠械。預(yù)發(fā)布環(huán)境就是測試基本完成由缆,交付給運營測試,改動基本比較小猾蒂。正式環(huán)境不用解釋均唉,不言而喻。
我們可以把多環(huán)境的配置寫在預(yù)編譯頭文件中:

/************環(huán)境配置開關(guān)**********
 * OPEN_TEST  0:為開發(fā)環(huán)境
 *            1:為測試環(huán)境
 *            2:為預(yù)發(fā)布外網(wǎng)環(huán)境
 *            其他:為生產(chǎn)環(huán)境
 ***************************/


#define OPEN_TEST 0

#if (OPEN_TEST == 0)/************開發(fā)環(huán)境************/
#define HTTPSURLEVER  @"http://www.runedu.test/api"


#elif (OPEN_TEST == 1)/************測試環(huán)境************/
#define HTTPSURLEVER  @"http://www.rjy.rd/api"


#elif (OPEN_TEST == 2)/************預(yù)發(fā)布環(huán)境************/
#define HTTPSURLEVER  @"http://www.prerjy.com/api"


#else/************生產(chǎn)環(huán)境************/
#define HTTPSURLEVER @"http://www.runjiaoyu.com.cn/api"

#endif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末婚夫,一起剝皮案震驚了整個濱河市浸卦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌案糙,老刑警劉巖限嫌,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異时捌,居然都是意外死亡怒医,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門奢讨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來稚叹,“玉大人,你說我怎么就攤上這事拿诸“切洌” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵亩码,是天一觀的道長季率。 經(jīng)常有香客問我,道長描沟,這世上最難降的妖魔是什么飒泻? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮吏廉,結(jié)果婚禮上泞遗,老公的妹妹穿的比我還像新娘。我一直安慰自己席覆,他們只是感情好史辙,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般髓霞。 火紅的嫁衣襯著肌膚如雪卦睹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天方库,我揣著相機與錄音结序,去河邊找鬼。 笑死纵潦,一個胖子當(dāng)著我的面吹牛徐鹤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播邀层,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼返敬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了寥院?” 一聲冷哼從身側(cè)響起劲赠,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎秸谢,沒想到半個月后凛澎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡估蹄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年塑煎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片臭蚁。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡最铁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出垮兑,到底是詐尸還是另有隱情冷尉,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布系枪,位于F島的核電站网严,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嗤无。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一怜庸、第九天 我趴在偏房一處隱蔽的房頂上張望当犯。 院中可真熱鬧,春花似錦割疾、人聲如沸嚎卫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拓诸。三九已至侵佃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間奠支,已是汗流浹背馋辈。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留倍谜,地道東北人迈螟。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像尔崔,于是被迫代替她去往敵國和親答毫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

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