你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS_第3方網絡請求_YTKNetwork

iOS_第3方網絡請求_YTKNetwork

編輯:IOS開發綜合

github地址:https://github.com/yuantiku/YTKNetwork/blob/master/ProGuide.md

YTKNetwork 是什麼

YTKNetwork 是猿題庫 iOS 研發團隊基於AFNetworking封裝的 iOS 網絡庫,其實現了一套 High Level 的 API,提供了更高層次的網絡訪問抽象。

YTKNetwork提供了哪些功能

相比 AFNetworking,YTKNetwork 提供了以下更高級的功能:

  • 支持按時間緩存網絡請求內容
  • 支持按版本號緩存網絡請求內容
  • 支持統一設置服務器和 CDN 的地址
  • 支持檢查返回 JSON 內容的合法性
  • 支持文件的斷點續傳
  • 支持blockdelegate兩種模式的回調方式
  • 支持批量的網絡請求發送,並統一設置它們的回調(實現在YTKBatchRequest類中)
  • 支持方便地設置有相互依賴的網絡請求的發送,例如:發送請求A,根據請求A的結果,選擇性的發送請求B和C,再根據B和C的結果,選擇性的發送請求D。(實現在YTKChainRequest類中)
  • 支持網絡請求 URL 的 filter,可以統一為網絡請求加上一些參數,或者修改一些路徑。
  • 定義了一套插件機制,可以很方便地為 YTKNetwork 增加功能。猿題庫官方現在提供了一個插件,可以在某些網絡請求發起時,在界面上顯示"正在加載"的 HUD。

哪些項目適合使用 YTKNetwork

YTKNetwork 適合稍微復雜一些的項目,不適合個人的小項目。

如果你的項目中需要緩存網絡請求、管理多個網絡請求之間的依賴、希望檢查服務器返回的 JSON 是否合法,那麼 YTKNetwork 能給你帶來很大的幫助。如果你緩存的網絡請求內容需要依賴特定版本號過期,那麼 YTKNetwork 就能發揮出它最大的優勢。

YTKNetwork 支持iOS 6或之後的版本。

YTKNetwork 的基本思想

YTKNetwork 的基本的思想是把每一個網絡請求封裝成對象。所以使用 YTKNetwork,你的每一個請求都需要繼承YTKRequest類,通過覆蓋父類的一些方法來構造指定的網絡請求。

把每一個網絡請求封裝成對象其實是使用了設計模式中的 Command 模式,它有以下好處:

  • 將網絡請求與具體的第三方庫依賴隔離,方便以後更換底層的網絡庫。實際上 YTKNetwork 最初是基於 ASIHttpRequest 的,我們只花了兩天,就很輕松地切換到了 AFNetworking。
  • 方便在基類中處理公共邏輯,例如猿題庫的數據版本號信息就統一在基類中處理。
  • 方便在基類中處理緩存邏輯,以及其它一些公共邏輯。
  • 方便做對象的持久化。

當然,如果說它有什麼不好,那就是如果你的工程非常簡單,這麼寫會顯得沒有直接用 AFNetworking 將請求邏輯寫在 Controller 中方便,所以 YTKNetwork 並不合適特別簡單的項目。

CocoaPods 支持

你可以在 Podfile 中加入下面一行代碼來使用YTKNetwork

pod 'YTKNetwork'

 

YTKNetwork 使用基礎教程

 

本教程將講解 YTKNetwork 的基本功能的使用。

YTKNetwork 基本組成

YTKNetwork 包括以下幾個基本的類:

  • YTKNetworkConfig 類:用於統一設置網絡請求的服務器和 CDN 的地址。
  • YTKRequest 類:所有的網絡請求類需要繼承於YTKRequest類,每一個YTKRequest類的子類代表一種專門的網絡請求。

接下來我們詳細地來解釋這些類以及它們的用法。

YTKNetworkConfig 類

YTKNetworkConfig 類有兩個作用:

  1. 統一設置網絡請求的服務器和 CDN 的地址。
  2. 管理網絡請求的 YTKUrlFilterProtocol 實例(在高級功能教程中有介紹)。

我們為什麼需要統一設置服務器地址呢?因為:

  1. 按照設計模式裡的Do Not Repeat Yourself原則,我們應該把服務器地址統一寫在一個地方。
  2. 在實際業務中,我們的測試人員需要切換不同的服務器地址來測試。統一設置服務器地址到 YTKNetworkConfig 類中,也便於我們統一切換服務器地址。

具體的用法是,在程序剛啟動的回調中,設置好 YTKNetworkConfig 的信息,如下所示:

- (BOOL)application:(UIApplication *)application 
   didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
   YTKNetworkConfig *config = [YTKNetworkConfig sharedInstance];
   config.baseUrl = @"http://yuantiku.com";
   config.cdnUrl = @"http://fen.bi";
}

設置好之後,所有的網絡請求都會默認使用 YTKNetworkConfig 中baseUrl參數指定的地址。

大部分企業應用都需要對一些靜態資源(例如圖片、js、css)使用CDN。YTKNetworkConfig的cdnUrl參數用於統一設置這一部分網絡請求的地址。

當我們需要切換服務器地址時,只需要修改 YTKNetworkConfig 中的baseUrlcdnUrl參數即可。

YTKRequest 類

YTKNetwork 的基本的思想是把每一個網絡請求封裝成對象。所以使用 YTKNetwork,你的每一種請求都需要繼承 YTKRequest類,通過覆蓋父類的一些方法來構造指定的網絡請求。把每一個網絡請求封裝成對象其實是使用了設計模式中的 Command 模式。

每一種網絡請求繼承 YTKRequest 類後,需要用方法覆蓋(overwrite)的方式,來指定網絡請求的具體信息。如下是一個示例:

假如我們要向網址http://www.yuantiku.com/iphone/register發送一個POST請求,請求參數是 username 和 password。那麼,這個類應該如下所示:

// RegisterApi.h
#import "YTKRequest.h"

@interface RegisterApi : YTKRequest

- (id)initWithUsername:(NSString *)username password:(NSString *)password;

@end


// RegisterApi.m


#import "RegisterApi.h"

@implementation RegisterApi {
    NSString *_username;
    NSString *_password;
}

- (id)initWithUsername:(NSString *)username password:(NSString *)password {
    self = [super init];
    if (self) {
        _username = username;
        _password = password;
    }
    return self;
}

- (NSString *)requestUrl {
    // “http://www.yuantiku.com” 在 YTKNetworkConfig 中設置,這裡只填除去域名剩余的網址信息
    return @"/iphone/register";
}

- (YTKRequestMethod)requestMethod {
    return YTKRequestMethodPost;
}

- (id)requestArgument {
    return @{
        @"username": _username,
        @"password": _password
    };
}

@end

在上面這個示例中,我們可以看到:

  • 我們通過覆蓋 YTKRequest 類的requestUrl方法,實現了指定網址信息。並且我們只需要指定除去域名剩余的網址信息,因為域名信息在 YTKNetworkConfig 中已經設置過了。
  • 我們通過覆蓋 YTKRequest 類的requestMethod方法,實現了指定 POST 方法來傳遞參數。
  • 我們通過覆蓋 YTKRequest 類的requestArgument方法,提供了 POST 的信息。這裡面的參數usernamepassword如果有一些特殊字符(如中文或空格),也會被自動編碼。

調用 RegisterApi

在構造完成 RegisterApi 之後,具體如何使用呢?我們可以在登錄的 ViewController中,調用 RegisterApi,並用block的方式來取得網絡請求結果:

- (void)loginButtonPressed:(id)sender {
    NSString *username = self.UserNameTextField.text;
    NSString *password = self.PasswordTextField.text;
    if (username.length > 0 && password.length > 0) {
        RegisterApi *api = [[RegisterApi alloc] initWithUsername:username password:password];
        [api startWithCompletionBlockWithSuccess:^(YTKBaseRequest *request) {
            // 你可以直接在這裡使用 self
            NSLog(@"succeed");

        } failure:^(YTKBaseRequest *request) {
            // 你可以直接在這裡使用 self
            NSLog(@"failed");
        }];
    }
}

注意:你可以直接在block回調中使用self,不用擔心循環引用。因為 YTKRequest 會在執行完 block 回調之後,將相應的 block 設置成 nil。從而打破循環引用。

除了block的回調方式外,YTKRequest 也支持 delegate 方式的回調:

- (void)loginButtonPressed:(id)sender {
    NSString *username = self.UserNameTextField.text;
    NSString *password = self.PasswordTextField.text;
    if (username.length > 0 && password.length > 0) {
        RegisterApi *api = [[RegisterApi alloc] initWithUsername:username password:password];
        api.delegate = self;
        [api start];
    }
}

- (void)requestFinished:(YTKBaseRequest *)request {
    NSLog(@"succeed");
}

- (void)requestFailed:(YTKBaseRequest *)request {
    NSLog(@"failed");
}

驗證服務器返回內容

有些時候,由於服務器的Bug,會造成服務器返回一些不合法的數據,如果盲目地信任這些數據,可能會造成客戶端Crash。如果加入大量的驗證代碼,又使得編程體力活增加,費時費力。

使用 YTKRequest 的驗證服務器返回值功能,可以很大程度上節省驗證代碼的編寫時間。

例如,我們要向網址http://www.yuantiku.com/iphone/users發送一個GET請求,請求參數是userId。我們想獲得某一個用戶的信息,包括他的昵稱和等級,我們需要服務器必須返回昵稱(字符串類型)和等級信息(數值類型),則可以覆蓋jsonValidator方法,實現簡單的驗證。

- (id)jsonValidator {
    return @{
        @"nick": [NSString class],
        @"level": [NSNumber class]
    };
}

完整的代碼如下:

// GetUserInfoApi.h
#import "YTKRequest.h"

@interface GetUserInfoApi : YTKRequest

- (id)initWithUserId:(NSString *)userId;

@end


// GetUserInfoApi.m
#import "GetUserInfoApi.h"

@implementation GetUserInfoApi {
    NSString *_userId;
}

- (id)initWithUserId:(NSString *)userId {
    self = [super init];
    if (self) {
        _userId = userId;
    }
    return self;
}

- (NSString *)requestUrl {
    return @"/iphone/users";
}

- (id)requestArgument {
    return @{ @"id": _userId };
}

- (id)jsonValidator {
    return @{
        @"nick": [NSString class],
        @"level": [NSNumber class]
    };
}

@end

以下是更多的jsonValidator的示例:

要求返回String數組:
- (id)jsonValidator {
    return @[ [NSString class] ];
}
來自猿題庫線上環境的一個復雜的例子:
- (id)jsonValidator {
    return @[@{
        @"id": [NSNumber class],
        @"imageId": [NSString class],
        @"time": [NSNumber class],
        @"status": [NSNumber class],
        @"question": @{
            @"id": [NSNumber class],
            @"content": [NSString class],
            @"contentType": [NSNumber class]
        }
    }];
} 

使用CDN地址

如果要使用CDN地址,只需要覆蓋 YTKRequest 類的- (BOOL)useCDN;方法。

例如我們有一個取圖片的接口,地址是http://fen.bi/image/imageId,則我們可以這麼寫代碼:

// GetImageApi.h
#import "YTKRequest.h"

@interface GetImageApi : YTKRequest
- (id)initWithImageId:(NSString *)imageId;
@end

// GetImageApi.m
#import "GetImageApi.h"

@implementation GetImageApi {
    NSString *_imageId;
}

- (id)initWithImageId:(NSString *)imageId {
    self = [super init];
    if (self) {
        _imageId = imageId;
    }
    return self;
}

- (NSString *)requestUrl {
    return [NSString stringWithFormat:@"/iphone/images/%@", _imageId];
}

- (BOOL)useCDN {
    return YES;
}

@end

斷點續傳

要啟動斷點續傳功能,只需要覆蓋resumableDownloadPath方法,指定斷點續傳時文件的暫存路徑即可。如下代碼將剛剛的取圖片的接口改造成了支持斷點續傳:

@implementation GetImageApi {
    NSString *_imageId;
}

- (id)initWithImageId:(NSString *)imageId {
    self = [super init];
    if (self) {
        _imageId = imageId;
    }
    return self;
}

- (NSString *)requestUrl {
    return [NSString stringWithFormat:@"/iphone/images/%@", _imageId];
}

- (BOOL)useCDN {
    return YES;
}

- (NSString *)resumableDownloadPath {
    NSString *libPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *cachePath = [libPath stringByAppendingPathComponent:@"Caches"];
    NSString *filePath = [cachePath stringByAppendingPathComponent:_imageId];
    return filePath;
}

@end

按時間緩存內容

剛剛我們寫了一個 GetUserInfoApi ,這個網絡請求是獲得用戶的一些資料。

我們想像這樣一個場景,假設你在完成一個類似微博的客戶端,GetUserInfoApi 用於獲得你的某一個好友的資料,因為好友並不會那麼頻繁地更改昵稱,那麼短時間內頻繁地調用這個接口很可能每次都返回同樣的內容,所以我們可以給這個接口加一個緩存。

在如下示例中,我們通過覆蓋cacheTimeInSeconds方法,給 GetUserInfoApi 增加了一個3分鐘的緩存,3分鐘內調用調Api的start方法,實際上並不會發送真正的請求。

@implementation GetUserInfoApi {
    NSString *_userId;
}

- (id)initWithUserId:(NSString *)userId {
    self = [super init];
    if (self) {
        _userId = userId;
    }
    return self;
}

- (NSString *)requestUrl {
    return @"/iphone/users";
}

- (id)requestArgument {
    return @{ @"id": _userId };
}

- (id)jsonValidator {
    return @{
        @"nick": [NSString class],
        @"level": [NSNumber class]
    };
}

- (NSInteger)cacheTimeInSeconds {
    // 3分鐘 = 180 秒
    return 60 * 3;
}

@end

該緩存邏輯對上層是透明的,所以上層可以不用考慮緩存邏輯,每次調用 GetUserInfoApi 的start方法即可。GetUserInfoApi只有在緩存過期時,才會真正地發送網絡請求。


 

 

YTKNetwork 使用高級教程

 

本教程將講解 YTKNetwork 的高級功能的使用。

YTKUrlFilterProtocol 接口

YTKUrlFilterProtocol 接口用於實現對網絡請求URL或參數的重寫,例如可以統一為網絡請求加上一些參數,或者修改一些路徑。

例如:在猿題庫中,我們需要為每個網絡請求加上客戶端的版本號作為參數。所以我們實現了如下一個YTKUrlArgumentsFilter類,實現了YTKUrlFilterProtocol接口:

// YTKUrlArgumentsFilter.h
@interface YTKUrlArgumentsFilter : NSObject 

+ (YTKUrlArgumentsFilter *)filterWithArguments:(NSDictionary *)arguments;

- (NSString *)filterUrl:(NSString *)originUrl withRequest:(YTKBaseRequest *)request;

@end


// YTKUrlArgumentsFilter.m
@implementation YTKUrlArgumentsFilter {
    NSDictionary *_arguments;
}

+ (YTKUrlArgumentsFilter *)filterWithArguments:(NSDictionary *)arguments {
    return [[self alloc] initWithArguments:arguments];
}

- (id)initWithArguments:(NSDictionary *)arguments {
    self = [super init];
    if (self) {
        _arguments = arguments;
    }
    return self;
}

- (NSString *)filterUrl:(NSString *)originUrl withRequest:(YTKBaseRequest *)request {
    return [YTKNetworkPrivate urlStringWithOriginUrlString:originUrl appendParameters:_arguments];
}

@end


通過以上YTKUrlArgumentsFilter類,我們就可以用以下代碼方便地為網絡請求增加統一的參數,如增加當前客戶端的版本號:


- (BOOL)application:(UIApplication *)application 
         didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [self setupRequestFilters];
    return YES;
}

- (void)setupRequestFilters {
    NSString *appVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
    YTKNetworkConfig *config = [YTKNetworkConfig sharedInstance];
    YTKUrlArgumentsFilter *urlFilter = [YTKUrlArgumentsFilter filterWithArguments:@{@"version": appVersion}];
    [config addUrlFilter:urlFilter];
}


YTKBatchRequest 類

YTKBatchRequest 類:用於方便地發送批量的網絡請求,YTKBatchRequest是一個容器類,它可以放置多個YTKRequest子類,並統一處理這多個網絡請求的成功和失敗。

在如下的示例中,我們發送了4個批量的請求,並統一處理這4個請求同時成功的回調。


#import "YTKBatchRequest.h"
#import "GetImageApi.h"
#import "GetUserInfoApi.h"

- (void)sendBatchRequest {
    GetImageApi *a = [[GetImageApi alloc] initWithImageId:@"1.jpg"];
    GetImageApi *b = [[GetImageApi alloc] initWithImageId:@"2.jpg"];
    GetImageApi *c = [[GetImageApi alloc] initWithImageId:@"3.jpg"];
    GetUserInfoApi *d = [[GetUserInfoApi alloc] initWithUserId:@"123"];
    YTKBatchRequest *batchRequest = [[YTKBatchRequest alloc] initWithRequestArray:@[a, b, c, d]];
    [batchRequest startWithCompletionBlockWithSuccess:^(YTKBatchRequest *batchRequest) {
        NSLog(@"succeed");
        NSArray *requests = batchRequest.requestArray;
        GetImageApi *a = (GetImageApi *)requests[0];
        GetImageApi *b = (GetImageApi *)requests[1];
        GetImageApi *c = (GetImageApi *)requests[2];
        GetUserInfoApi *user = (GetUserInfoApi *)requests[3];
        // deal with requests result ...
    } failure:^(YTKBatchRequest *batchRequest) {
        NSLog(@"failed");
    }];
}

YTKChainRequest 類

用於管理有相互依賴的網絡請求,它實際上最終可以用來管理多個拓撲排序後的網絡請求。

例如,我們有一個需求,需要用戶在注冊時,先發送注冊的Api,然後:

  • 如果注冊成功,再發送讀取用戶信息的Api。並且,讀取用戶信息的Api需要使用注冊成功返回的用戶id號。
  • 如果注冊失敗,則不發送讀取用戶信息的Api了。

以下是具體的代碼示例,在示例中,我們在sendChainRequest方法中設置好了Api相互的依賴,然後。 我們就可以通過chainRequestFinished回調來處理所有網絡請求都發送成功的邏輯了。如果有任何其中一個網絡請求失敗了,則會觸發chainRequestFailed回調。

- (void)sendChainRequest {
    RegisterApi *reg = [[RegisterApi alloc] initWithUsername:@"username" password:@"password"];
    YTKChainRequest *chainReq = [[YTKChainRequest alloc] init];
    [chainReq addRequest:reg callback:^(YTKChainRequest *chainRequest, YTKBaseRequest *baseRequest) {
        RegisterApi *result = (RegisterApi *)baseRequest;
        NSString *userId = [result userId];
        GetUserInfoApi *api = [[GetUserInfoApi alloc] initWithUserId:userId];
        [chainRequest addRequest:api callback:nil];

    }];
    chainReq.delegate = self;
    // start to send request
    [chainReq start];
}

- (void)chainRequestFinished:(YTKChainRequest *)chainRequest {
    // all requests are done
}

- (void)chainRequestFailed:(YTKChainRequest *)chainRequest failedBaseRequest:(YTKBaseRequest*)request {
    // some one of request is failed
}

顯示上次緩存的內容

在實際開發中,有一些內容可能會加載很慢,我們想先顯示上次的內容,等加載成功後,再用最新的內容替換上次的內容。也有時候,由於網絡處於斷開狀態,為了更加友好,我們想顯示上次緩存中的內容。這個時候,可以使用 YTKReqeust 的直接加載緩存的高級用法。

具體的方法是直接使用YTKRequest- (id)cacheJson方法,即可獲得上次緩存的內容。當然,你需要把- (NSInteger)cacheTimeInSeconds覆蓋,返回一個大於等於0的值,這樣才能開啟YTKRequest的緩存功能,否則默認情況下,緩存功能是關閉的。

以下是一個示例,我們在加載用戶信息前,先取得上次加載的內容,然後再發送請求,請求成功後再更新界面:


- (void)loadCacheData {
    NSString *userId = @"1";
    GetUserInfoApi *api = [[GetUserInfoApi alloc] initWithUserId:userId];
    if ([api cacheJson]) {
        NSDictionary *json = [api cacheJson];
        NSLog(@"json = %@", json);
        // show cached data
    }
    [api startWithCompletionBlockWithSuccess:^(YTKBaseRequest *request) {
        NSLog(@"update ui");
    } failure:^(YTKBaseRequest *request) {
        NSLog(@"failed");
    }];
}

上傳文件

我們可以通過覆蓋constructingBodyBlock方法,來方便地上傳圖片等附件,如下是一個示例:

// YTKRequest.h
#import "YTKRequest.h"

@interface UploadImageApi : YTKRequest

- (id)initWithImage:(UIImage *)image;
- (NSString *)responseImageId;

@end

// YTKRequest.m
@implementation UploadImageApi {
    UIImage *_image;
}

- (id)initWithImage:(UIImage *)image {
    self = [super init];
    if (self) {
        _image = image;
    }
    return self;
}

- (YTKRequestMethod)requestMethod {
    return YTKRequestMethodPost;
}

- (NSString *)requestUrl {
    return @"/iphone/image/upload";
}

- (AFConstructingBlock)constructingBodyBlock {
    return ^(id formData) {
        NSData *data = UIImageJPEGRepresentation(_image, 0.9);
        NSString *name = @"image";
        NSString *formKey = @"image";
        NSString *type = @"image/jpeg";
        [formData appendPartWithFileData:data name:formKey fileName:name mimeType:type];
    };
}

- (id)jsonValidator {
    return @{ @"imageId": [NSString class] };
}

- (NSString *)responseImageId {
    NSDictionary *dict = self.responseJSONObject;
    return dict[@"imageId"];
}

@end

通過如上代碼,我們創建了一個上傳圖片,然後獲得服務器返回的 imageId 的網絡請求Api。

定制網絡請求的HeaderField

通過覆蓋requestHeaderFieldValueDictionary方法返回一個dictionary對象來自定義請求的HeaderField,返回的dictionary,其key即為HeaderField的key,value為HeaderField的Value,需要注意的是key和value都必須為string對象。

定制buildCustomUrlRequest

通過覆蓋buildCustomUrlRequest方法,返回一個NSUrlRequest對象來達到完全自定義請求的需求。該方法定義在YTKBaseRequest類,如下:

// 構建自定義的UrlRequest,
// 若這個方法返回非nil對象,會忽略requestUrl, requestArgument, requestMethod, requestSerializerType,requestHeaderFieldValueDictionary
- (NSURLRequest *)buildCustomUrlRequest;

如注釋所言,如果構建自定義的request,會忽略其他的一切自定義request的方法,例如requestUrl,requestArgument,requestMethod,requestSerializerType,requestHeaderFieldValueDictionary。一個上傳gzippingData的示例如下:

- (NSURLRequest *)buildCustomUrlRequest {
    NSData *rawData = [[_events jsonString] dataUsingEncoding:NSUTF8StringEncoding];
    NSData *gzippingData = [NSData gtm_dataByGzippingData:rawData];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.requestUrl]];
    [request setHTTPMethod:@"POST"];
    [request addValue:@"application/json;charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
    [request addValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
    [request setHTTPBody:gzippingData];
    return request;
}
 

 

 

 

 

 

首先

 

  關於網絡層最先可能想到的是AFNetworking,或者Swift中的Alamofire,直接使用起來也特別的簡單,但是稍復雜的項目如果直接使用就顯得不夠用了,首先第三方耦合不說,就光散落在各處的請求回調就難以後期維護,所以一般會有針對性的再次封裝,往往初期可能業務相對簡單,考慮的方面較少,後期業務增加可能需要對網絡層進行重構,一個好的架構也一定是和業務層面緊密相連的,隨業務的增長不斷健壯的。
  最近也是看了YTKNetwork的源碼和相關博客,站在前輩的肩膀上寫下一些自己關於網絡層的解讀。

與業務層對接方式

常見的與業務層對接方式兩種:

集約型:
  最典型就屬於上面說的AFNetworkingAlamofire,發起網絡請求都集中在一個類上,請求回調通過Block、閉包實現的,Block、閉包回調有比較好的靈活性,可以方便的在任何位置發起請求,同時也可能是不好的地方,網絡請求回調散落在各處,不便於維護。
  下面是一個集約型的網絡請求,大家使用集約型網絡請求有沒有遇到這麼一個場景,請求回調後需要做比較多的處理,代碼量多的時候,會再定義一個私有方法把代碼寫在裡面,在Block中調用在私有方法。其實這樣處理本質上和通過代理回調本質上是一樣的。
[_manager GET:url parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
     //The data processing, Rendering interface
 } failure:^(AFHTTPRequestOperation *operation, NSError *error) {

 }];
離散型:
  對應的是接下來的YTKNetwork,離散型最大的特點就是一個網絡請求對應一個單獨的類,在這個類內部封裝請求地址、方式、參數、校驗和處理請求回來的數據,通常代理回調,需要跨層數據傳遞時也使用通知回調,比較集中,因為數據處理都放在內部處理了,返回數據的形式(模型化後的數據還是其他)不需要控制器關心,控制器只需要在代理返回的數據可以直接對渲染UI,讓Controller更加輕量化。

發起請求

    NSString *userId = @"1";
    GetUserInfoApi *api = [[GetUserInfoApi alloc] initWithUserId:userId];
    [api start];
    api.delegate = self;

Delegate回調

- (void)requestFinished:(YTKBaseRequest *)request {
    NSLog(@"----- succeed ---- %@", request.responseJSONObject);
    //Rendering interface
}
- (void)requestFailed:(YTKBaseRequest *)request {
    NSLog(@"failed");
}
YTKNetwork解析

首先看下YTKNetwork的類文件:

\
YTKNetwork.png

圖解它們之間的調用關系,注意還是理順關系,看懂這個圖應該對源碼的理解沒有太多問題:

\
Scrren.png YTKBaseRequest:YTKRequest的父類,定義了Request的相關屬性,Block和Delegate。給對外接口默認的實現,以及公共邏輯。 YTKRequest:主要對緩存做處理,更新緩存、讀取緩存、手動寫入緩存,是否忽略緩存。這裡采用歸檔形式緩存,請求方式、根路徑、請求地址、請求參數、app版本號、敏感數據拼接再MD5作為緩存的文件名,保證唯一性。還提供設置緩存的保存時長,主要實現是通過獲取緩存文件上次修改的時刻距離現在的時間和設置的緩存時長作比較,來判斷是否真正發起請求,下面是發起請求的一些邏輯判斷:
- (void)start {
    if (self.ignoreCache) {
        //如果忽略緩存 -> 網絡請求
        [super start];
        return;
    }

    // check cache time
    if ([self cacheTimeInSeconds] < 0) {
        //驗證緩存有效時間 -> 網絡請求
        [super start];
        return;
    }

    // check cache version
    long long cacheVersionFileContent = [self cacheVersionFileContent];
    if (cacheVersionFileContent != [self cacheVersion]) {
        //驗證緩存版本號,如果不一致 -> 網絡請求
        [super start];
        return;
    }

    // check cache existance
    NSString *path = [self cacheFilePath];  //
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:path isDirectory:nil]) {
        //根據文件路徑,驗證緩存是否存在,不存在 -> 網絡請求
        [super start];
        return;
    }

    // check cache time 上次緩存文件時刻距離現在的時長 與 緩存有效時間 對比
    int seconds = [self cacheFileDuration:path];
    if (seconds < 0 || seconds > [self cacheTimeInSeconds]) {
        //上次緩存文件時刻距離現在的時長 > 緩存有效時間
        [super start];
        return;
    }

    // load cache
    _cacheJson = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    if (_cacheJson == nil) {    //取出緩存,如果沒有 -> 網絡請求
        [super start];
        return;
    }

    _dataFromCache = YES;
    //緩存請求成功後的數據

    [self requestCompleteFilter];   //代理

    YTKRequest *strongSelf = self;
    [strongSelf.delegate requestFinished:strongSelf];

    if (strongSelf.successCompletionBlock) {    //block回調
        strongSelf.successCompletionBlock(strongSelf);
    }
    [strongSelf clearCompletionBlock];
}

通過歸檔存儲網絡請求的數據:

- (void)saveJsonResponseToCacheFile:(id)jsonResponse {
    if ([self cacheTimeInSeconds] > 0 && ![self isDataFromCache]) {
        NSDictionary *json = jsonResponse;
        if (json != nil) {
            [NSKeyedArchiver archiveRootObject:json toFile:[self cacheFilePath]];
            [NSKeyedArchiver archiveRootObject:@([self cacheVersion]) toFile:[self cacheVersionFilePath]];
        }
    }
}
YTKNetworkAgent:真正發起網絡請求的類,在addRequest方法裡調用AFN的方法,這塊可以方便的更換第三方庫,還包括一些請求取消,插件的代理方法調用等,所有網絡請求失敗或者成功都會調用下面這個方法:
- (void)handleRequestResult:(AFHTTPRequestOperation *)operation {
    NSString *key = [self requestHashKey:operation];
    YTKBaseRequest *request = _requestsRecord[key];
    YTKLog(@"Finished Request: %@", NSStringFromClass([request class]));
    if (request) {

        BOOL succeed = [self checkResult:request];
        if (succeed) {  //請求成功
            [request toggleAccessoriesWillStopCallBack];    //調用執行加載動畫插件
            [request requestCompleteFilter];
            if (request.delegate != nil) {  //請求成功代理回調
                [request.delegate requestFinished:request];
            }
            if (request.successCompletionBlock) {   //請求成功Block回調
                request.successCompletionBlock(request);
            }
            [request toggleAccessoriesDidStopCallBack];
        } else {            //請求失敗
            YTKLog(@"Request %@ failed, status code = %ld",
                     NSStringFromClass([request class]), (long)request.responseStatusCode);
            [request toggleAccessoriesWillStopCallBack];    //調用執行加載動畫插件
            [request requestFailedFilter];
            if (request.delegate != nil) {      //請求失敗代理回調
                [request.delegate requestFailed:request];
            }
            if (request.failureCompletionBlock) {   //請求失敗Block回調
                request.failureCompletionBlock(request);
            }
            [request toggleAccessoriesDidStopCallBack];
        }
    }
    [self removeOperation:operation];
    [request clearCompletionBlock];
}

YTKNetworkConfig:配置請求根路徑、DNS地址。

YTKNetworkPrivate:可以理解為一個工具類,拼接地址,提供加密方法,定義分類等。

YTKBatchRequestYTKChainRequest:這是YKTNetwork的兩個高級用法,批量網絡請求和鏈式的網絡請求,相當於一個存放Request的容器,先定義下面屬性,finishedCount來記錄批量請求的完成的個數:

@interface YTKBatchRequest() 
@property (nonatomic) NSInteger finishedCount;
@end

每完成一個請求finishedCount++,直到finishedCount等於所有請求的個數時才回調成功。

#pragma mark - Network Request Delegate

- (void)requestFinished:(YTKRequest *)request {
    _finishedCount++;
    if (_finishedCount == _requestArray.count) {
        [self toggleAccessoriesWillStopCallBack];
        if ([_delegate respondsToSelector:@selector(batchRequestFinished:)]) {
            [_delegate batchRequestFinished:self];
        }
        if (_successCompletionBlock) {
            _successCompletionBlock(self);
        }
        [self clearCompletionBlock];
        [self toggleAccessoriesDidStopCallBack];
    }
}

給Request綁定一個Index

@interface YTKChainRequest()
@property (assign, nonatomic) NSUInteger nextRequestIndex;
@end

從requestArray數組中依次取出發起網絡請求,同時nextRequestIndex++,只要一個請求失敗則觸發失敗的回調:

- (void)start {
    if (_nextRequestIndex > 0) {
        YTKLog(@"Error! Chain request has already started.");
        return;
    }
    if ([_requestArray count] > 0) {
        [self toggleAccessoriesWillStartCallBack];
        [self startNextRequest];
        [[YTKChainRequestAgent sharedInstance] addChainRequest:self];
    } else {
        YTKLog(@"Error! Chain request array is empty.");
    }
}
//下一個網絡請求
- (BOOL)startNextRequest {
    if (_nextRequestIndex < [_requestArray count]) {
        YTKBaseRequest *request = _requestArray[_nextRequestIndex];
        _nextRequestIndex++;
        request.delegate = self;
        [request start];
        return YES;
    } else {
        return NO;
    }
}
 
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved