标签:
github地址:https://github.com/yuantiku/YTKNetwork/blob/master/ProGuide.md
YTKNetwork 是猿题库 iOS 研发团队基于 AFNetworking 封装的 iOS 网络库,其实现了一套 High Level 的 API,提供了更高层次的网络访问抽象。
相比 AFNetworking,YTKNetwork 提供了以下更高级的功能:
block
和 delegate
两种模式的回调方式YTKBatchRequest
类中)YTKChainRequest
类中)YTKNetwork 适合稍微复杂一些的项目,不适合个人的小项目。
如果你的项目中需要缓存网络请求、管理多个网络请求之间的依赖、希望检查服务器返回的 JSON 是否合法,那么 YTKNetwork 能给你带来很大的帮助。如果你缓存的网络请求内容需要依赖特定版本号过期,那么 YTKNetwork 就能发挥出它最大的优势。
YTKNetwork 支持iOS 6或之后的版本。
YTKNetwork 的基本的思想是把每一个网络请求封装成对象。所以使用 YTKNetwork,你的每一个请求都需要继承YTKRequest
类,通过覆盖父类的一些方法来构造指定的网络请求。
把每一个网络请求封装成对象其实是使用了设计模式中的 Command 模式,它有以下好处:
当然,如果说它有什么不好,那就是如果你的工程非常简单,这么写会显得没有直接用 AFNetworking 将请求逻辑写在 Controller 中方便,所以 YTKNetwork 并不合适特别简单的项目。
你可以在 Podfile 中加入下面一行代码来使用YTKNetwork
pod ‘YTKNetwork‘
本教程将讲解 YTKNetwork 的基本功能的使用。
YTKNetwork 包括以下几个基本的类:
YTKRequest
类,每一个YTKRequest
类的子类代表一种专门的网络请求。接下来我们详细地来解释这些类以及它们的用法。
YTKNetworkConfig 类有两个作用:
我们为什么需要统一设置服务器地址呢?因为:
Do Not Repeat Yourself
原则,我们应该把服务器地址统一写在一个地方。具体的用法是,在程序刚启动的回调中,设置好 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 中的 baseUrl
和cdnUrl
参数即可。
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
在上面这个示例中,我们可以看到:
requestUrl
方法,实现了指定网址信息。并且我们只需要指定除去域名剩余的网址信息,因为域名信息在 YTKNetworkConfig 中已经设置过了。requestMethod
方法,实现了指定 POST 方法来传递参数。requestArgument
方法,提供了 POST 的信息。这里面的参数 username
和 password
如果有一些特殊字符(如中文或空格),也会被自动编码。在构造完成 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的示例:
- (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地址,只需要覆盖 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 的高级功能的使用。
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是一个容器类,它可以放置多个 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");
}];
}
用于管理有相互依赖的网络请求,它实际上最终可以用来管理多个拓扑排序后的网络请求。
例如,我们有一个需求,需要用户在注册时,先发送注册的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。
通过覆盖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的源码和相关博客,站在前辈的肩膀上写下一些自己关于网络层的解读。
常见的与业务层对接方式两种:
AFNetworking
、Alamofire
,发起网络请求都集中在一个类上,请求回调通过Block、闭包实现的,Block、闭包回调有比较好的灵活性,可以方便的在任何位置发起请求,同时也可能是不好的地方,网络请求回调散落在各处,不便于维护。[_manager GET:url parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
//The data processing, Rendering interface
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
发起请求
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的类文件:
图解它们之间的调用关系,注意还是理顺关系,看懂这个图应该对源码的理解没有太多问题:
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
:可以理解为一个工具类,拼接地址,提供加密方法,定义分类等。
YTKBatchRequest
、YTKChainRequest
:这是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;
}
}
标签:
原文地址:http://blog.csdn.net/pre_eminent/article/details/51607612