iOS_第3方网络请求_YTKNetwork

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

YTKNetwork
是什么

YTKNetwork 是猿题库 iOS 研发团队基于 AFNetworking 封装的 iOS 网络库,其实现了一套 High Level 的 API,提供了更高层次的网络访问抽象。

YTKNetwork提供了哪些功能

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

  • 支持按时间缓存网络请求内容
  • 支持按版本号缓存网络请求内容
  • 支持统一设置服务器和 CDN 的地址
  • 支持检查返回 JSON 内容的合法性
  • 支持文件的断点续传
  • 支持 block 和 delegate 两种模式的回调方式
  • 支持批量的网络请求发送,并统一设置它们的回调(实现在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 的信息。这里面的参数 username 和 password 如果有一些特殊字符(如中文或空格),也会被自动编码。

调用
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 <YTKUrlFilterProtocol>

+ (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<AFMultipartFormData> 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的方法,例如requestUrlrequestArgument,requestMethodrequestSerializerType,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() <YTKRequestDelegate>
@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()<YTKRequestDelegate>
@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;
    }
}

时间: 2024-08-05 19:28:31

iOS_第3方网络请求_YTKNetwork的相关文章

iOS_网络请求_代理方式

#pragma mark - 网络请求代理方式(异步) - (IBAction)DelegateButtonDidClicked:(UIButton *)sender { // 1.拼接 urlString,网址里面必须写 http:// NSString *urlString = @"http://ipad-bjwb.bjd.com.cn/DigitalPublication/publish/Handler/APINewsList.ashx? date=20131129&startRe

ios编程之网络请求

网络请求有GET请求和POST请求,get和post实现的时候可以选择同步或者异步实现.看一个请求是GET还是POST就看网址后面有没有携带请求体. GET与POST 区别 1.get请求 请求的网址全部明文显示 安全性不高 2.get请求 请求的网址 有字符数的限制 大概255个 3.post请求 请求的网址 不光是有一个请求的网址 还可以携带请求体 这个请求体 是以NSData形式存在 安全性较高 4.post请求没有字符数的限制 GET同步和GET异步 同步请求是在请求数据的时候不能做其他

Android 各大网络请求库的比较及实战,android请求库实战

自己学习android也有一段时间了,在实际开发中,频繁的接触网络请求,而网络请求的方式很多,最常见的那么几个也就那么几个.本篇文章对常见的网络请求库进行一个总结. HttpUrlConnection 最开始学android的时候用的网络请求是HttpUrlConnection,当时很多东西还不知道,但是在android 2.2及以下版本中HttpUrlConnection存在着一些bug,所以建议在android 2.3以后使用HttpUrlConnection,之前使用HttpClient.

Volley——网络请求

Volley作为当年Google在2013年的Google I/O上的重点,是一个相当给力的框架.它从设计模式上来说,非常具有扩展性,也比较轻巧.关于Volley的使用,网上介绍的很多了,不再赘述.现在,我将记录我阅读Volley源码的过程,来学习Volley的设计思想和其中的一些小技巧. 值的一提的是,新版的gradle已经支持: compile 'com.android.volley:volley:1.0.0' 这样导入Volley了. 从最简单的例子看起: RequestQueue que

AFNetWorking 网络请求转载

今天开始会写几篇关于AFN源码解读的一些Blog,首先要梳理一下AFN的整体结构(主要是讨论2.x版本的Session访问模块):我们先看看我们最常用的一段代码: AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; [manager GET:@"https://www.baidu.com" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task

封装网络请求

封装网络请求类便捷, 适合多个地方使用. 第一封装下载方法 前提用Cocoapods或者直接引用第三方AFNetWorking 定义一个类NetWorkingManager, 继承于NSObject A: 在.h中写方法声明 #import <Foundation/Foundation.h> @interface NetWorkManager : NSObject - (void)downLoadWithUrl:(NSString *)url progress:(void(^)(float p

iOS开发——实战篇Swift篇&amp;UItableView结合网络请求,多线程,数据解析,MVC实战

UItableView结合网络请求,多线程,数据解析,MVC实战 学了这么久的swift都没有做过什么东西,今天就以自己的一个小小的联系,讲一下,怎么使用swift在实战中应用MVC,并且结合后面的高级知识:网络请求,JSON数据解析一起应用到一个项目中来. 好了,废话不多说,我们直接开始吧. 首先看看最终的效果: 是不是很简单,就是个UItableView显示一些简单的数据,如果你真的觉得太简单了,那么请绕道,寻找更深入东西,但或者没有你想的那么简单,这不仅仅是一个tableView,为什么呢

iOS 通过网络请求获取图片的下载歌曲

1.导入代理<NSURLConnectionDataDelegate> 1 @interface ViewController ()<NSURLConnectionDataDelegate> 2 { 3 long long alllength; //下载总长度 4 long long currlenth; //当期下载长度 5 } 6 //存放下载的mp3 数据流 7 @property(nonatomic,strong)NSMutableData *msicDate; 8 //下

用Alamofire进行网络请求的一段代码解析

/* 向服务器发送请求: request实际上有四个参数,但是后两个参数都设定为了默认值,所以在调用的时候只要初始化前两个参数就可以. 第一个参数是设定向服务器发起的请求的方法,通常为GET方法和POST方法. 第二个参数就是URL地址. 第三个参数是填写一些必要的信息,比如说使用密钥. 该方法返回一个Alamofire.Request对象 responseJSON方法有一个必包作为参数.这个必包有四个参数,没有返回值.但是responseJSON有一个Alamofire.request的返回值