离散型网络框架YTKNetwork源码分析

本人有若干成套学习视频, 可试看! 可试看! 可试看, 重要的事情说三遍 包含Java, 数据结构与算法, iOS, 安卓, python, flutter等等, 如有需要, 联系微信tsaievan.

YTKNetwork是对AFNetworking的二次封装, 是离散型网络请求层的典型代表.
它的特点就是把每一个网络请求都抽象成一个对象.

这些网络请求的基类就是YTKBaseRequest.

试想一下, 一个完整网络请求需要哪些要素:

  • 请求地址
  • 请求头
  • 请求体
  • 发起请求
  • 收到服务器的响应头
  • 响应体

那么这个YTKBaseRequest就是将所有这些进行了封装

在发起请求的时候, 我们需要实例化一个request对象, 重写一些方法:

///=============================================================================
/// @name 子类重写
///=============================================================================

///< 请求成功后, 切换到主线程之前, 在子线程调用
///< 注意: 如果是缓存加载, 这个方法将在主线程调用, 和`requestCompleteFilter`方法一样
- (void)requestCompletePreprocessor;

///< 请求成功后在主线程调用
- (void)requestCompleteFilter;

///< 请求失败后, 切换到主线程之前, 在子线程调用
///< 注意: 如果是缓存加载, 这个方法将在主线程调用
- (void)requestFailedPreprocessor;

///< 请求失败后在主线程调用
- (void)requestFailedFilter;

///< 请求的baseURL, 只包含URL的主机名部分, 例如, http://www.example.com.
- (NSString *)baseUrl;

///< 请求URL的路径部分, 只包含URL的路径部分, 例如:/v1/user
- (NSString *)requestUrl;

///< CDN URL
- (NSString *)cdnUrl;

///< 请求的超时时长, 默认60秒
///< 当设置了`resumableDownloadPath`(NSURLSessionDownloadTask)属性时, 会话会忽略`timeoutInterval`属性, 一种有效的设置超时时长的方法是设置`NSURLSessionConfiguration`的`timeoutIntervalForResource`属性.
- (NSTimeInterval)requestTimeoutInterval;

///< 额外的请求参数
- (nullable id)requestArgument;

///  Override this method to filter requests with certain arguments when caching.
- (id)cacheFileNameFilterForRequestArgument:(id)argument;

///  HTTP 请求方法 "GET", "POST"这些
- (YTKRequestMethod)requestMethod;

///  Request 请求序列化类型 "HTTP", "JSON"
- (YTKRequestSerializerType)requestSerializerType;

///  Response 响应序列化类型 "HTTP", "JSON"
- (YTKResponseSerializerType)responseSerializerType;

///< HTTP授权要使用的用户名和密码, 必须写成@[@"Username", @"Password"]这种格式
- (nullable NSArray *)requestAuthorizationHeaderFieldArray;

///  添加的请求头信息
- (nullable NSDictionary *)requestHeaderFieldValueDictionary;

///< 自定义一个`NSURLRequest`对象, 当这个返回不为空时,`requestUrl`, `requestTimeoutInterval`,
///  `requestArgument`, `allowsCellularAccess`, `requestMethod`和 `requestSerializerType`方法将被忽略
- (nullable NSURLRequest *)buildCustomUrlRequest;

///  发送请求时是否使用CDN
- (BOOL)useCDN;

///  发起请求时是否使用蜂窝网络, 默认是YES
- (BOOL)allowsCellularAccess;

///  检测返回的json对象是不是标准json格式
- (nullable id)jsonValidator;

///  检测响应状态码是否有效
- (BOOL)statusCodeValidator;

重写了这些方法以后, 一个HTTP基本请求的要求就满足了, 有请求地址, 请求头request.

然后就是发起请求了:

基类里面提供了很简单的三个方法:

///=============================================================================
/// @name 请求行为
///=============================================================================

///  添加自己到请求队列, 并发起请求
- (void)start;

///  将自己移除出请求队列, 并终止请求
- (void)stop;

///  发起请求, 包含成功和失败回调
- (void)startWithCompletionBlockWithSuccess:(nullable YTKRequestCompletionBlock)success
                                    failure:(nullable YTKRequestCompletionBlock)failure;

发起请求和终止请求的实现:

- (void)start {
    [self toggleAccessoriesWillStartCallBack];
    [[YTKNetworkAgent sharedAgent] addRequest:self];
}

- (void)stop {
    [self toggleAccessoriesWillStopCallBack];
    self.delegate = nil;
    [[YTKNetworkAgent sharedAgent] cancelRequest:self];
    [self toggleAccessoriesDidStopCallBack];
}
这里面的核心方法就是[[YTKNetworkAgent sharedAgent] addRequest:self][[YTKNetworkAgent sharedAgent] cancelRequest:self].

这里就涉及到YTKNetworkAgent这个单例对象了, 单例对象的核心方法就这两个:

///  将请求添加进会话, 并发起请求
- (void)addRequest:(YTKBaseRequest *)request;

///  将之前添加进会话的请求取消
- (void)cancelRequest:(YTKBaseRequest *)request;

- (void)addRequest:(YTKBaseRequest *)request;方法正如注释上写的, 就做了两件是

  • 将请求添加进会话, 返回task对象
  • 发起请求, [task resume]
不过细看下来,当然没那么简单, 里面做了很多逻辑. 先看添加请求进会话, 发起请求这个方法:
  • 首先看了buildCustomUrlRequest这个方法有没有重写
    - 如果重写了, 就直接根据自定义的request创建task, 直接调用AFHTTPSessionManager的方法去创建task, 然后在完成回调里面处理数据, 填充requestresponse数据等等.
    - 如果没重写, 那么就要重新创建task对象, 间接调用AFHTTPSessionManager的方法, 但本质上都是调用AFN, 因为YTKNetwork本身就是对AFN的二次封装.

  • iOS 8.0以上系统, 还会对task设定优先级

  • request对象添加进一个字典做内存缓存

  • 发起请求[request.requestTask resume]

- (void)addRequest:(YTKBaseRequest *)request {
    NSParameterAssert(request != nil);

    NSError * __autoreleasing requestSerializationError = nil;

    NSURLRequest *customUrlRequest= [request buildCustomUrlRequest];
    if (customUrlRequest) {
        __block NSURLSessionDataTask *dataTask = nil;
        dataTask = [_manager dataTaskWithRequest:customUrlRequest completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
            [self handleRequestResult:dataTask responseObject:responseObject error:error];
        }];
        request.requestTask = dataTask;
    } else {
        request.requestTask = [self sessionTaskForRequest:request error:&requestSerializationError];
    }

    if (requestSerializationError) {
        [self requestDidFailWithRequest:request error:requestSerializationError];
        return;
    }

    NSAssert(request.requestTask != nil, @"requestTask should not be nil");

    // Set request task priority
    // !!Available on iOS 8 +
    if ([request.requestTask respondsToSelector:@selector(priority)]) {
        switch (request.requestPriority) {
            case YTKRequestPriorityHigh:
                request.requestTask.priority = NSURLSessionTaskPriorityHigh;
                break;
            case YTKRequestPriorityLow:
                request.requestTask.priority = NSURLSessionTaskPriorityLow;
                break;
            case YTKRequestPriorityDefault:
                /*!!fall through*/
            default:
                request.requestTask.priority = NSURLSessionTaskPriorityDefault;
                break;
        }
    }

    // Retain request
    YTKLog(@"Add request: %@", NSStringFromClass([request class]));
    [self addRequestToRecord:request];
    [request.requestTask resume];
}
然后看取消请求的方法:
- (void)cancelRequest:(YTKBaseRequest *)request {
    NSParameterAssert(request != nil);

    if (request.resumableDownloadPath) {
        NSURLSessionDownloadTask *requestTask = (NSURLSessionDownloadTask *)request.requestTask;
        [requestTask cancelByProducingResumeData:^(NSData *resumeData) {
            NSURL *localUrl = [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath];
            [resumeData writeToURL:localUrl atomically:YES];
        }];
    } else {
        [request.requestTask cancel];
    }

    [self removeRequestFromRecord:request];
    [request clearCompletionBlock];
}
  • 核心方法就是调用[task cancel]方法
  • 然后将请求移除出缓存
最后还有一个取消所有请求方法, 思路就是将缓存中的请求遍历, 然后逐一取消.

前面说了, YTKNetwork是一个离散型的网络框架, 那么, 它的每一个请求其实都是继承自YTKRequest的, 而不是YTKBaseRequest, YTKRequest相对于YTKBaseRequest增加了缓存处理.
当你继承自YTKRequest的时候, 可以通过重写父类方法, 自定义一些缓存策略.

下面是子类需要去重写的方法:

///< 当你创建自己的请求类的时候, 你必须继承YTKRequest,YTKRequest又继承自YTKBaseRequest
///< YTKRequest在YTKBaseRequest的基础上添加了本地缓存特征
///< 但需要注意的是, 下载请求不会做缓存, 因为下载请求涉及到复杂的缓存控制策略, 比如`Cache-Control`, `Last-Modified`的控制
@interface YTKRequest : YTKBaseRequest

///< 是否缓存响应
///< 默认为NO, 当遇到一些特殊的参数时, 缓存才会起作用
///< cacheTimeInSeconds 默认 -1秒, 缓存数据不会使用,除非你返回一个正值
///< 如果这个属性是YES, 那么响应数据将一直被存储
@property (nonatomic) BOOL ignoreCache;

///  数据是否是从本地缓存中来的
- (BOOL)isDataFromCache;

///  手动加载缓存
///
///  缓存加载失败, error有值, 否则为NULL
///
///  @return 缓存是否成功被加载
- (BOOL)loadCacheWithError:(NSError * __autoreleasing *)error;

///< 不管本地是否有缓存都发起请求, 使用这个方法更新本地缓存
- (void)startWithoutCache;

///  保存响应数据到本地的缓存路径
- (void)saveResponseDataToCacheFile:(NSData *)data;

#pragma mark - Subclass Override

///< 默认为 -1. 表示不缓存
///< 设置最大的磁盘缓存时间
- (NSInteger)cacheTimeInSeconds;

///< 可以用来确定本地缓存, 和让本地缓存失效, 默认是0
- (long long)cacheVersion;

///< 用来告诉缓存需要跟新的额外标识符, 推荐返回数组或者字典
- (nullable id)cacheSensitiveData;

///  异步写缓存, 默认为YES
- (BOOL)writeCacheAsynchronously;

YTKRequest这个类中, 重写了start方法:

- (void)start {
    if (self.ignoreCache) {
        [self startWithoutCache];
        return;
    }

    // Do not cache download request.
    if (self.resumableDownloadPath) {
        [self startWithoutCache];
        return;
    }

    if (![self loadCacheWithError:nil]) {
        [self startWithoutCache];
        return;
    }

    _dataFromCache = YES;

    dispatch_async(dispatch_get_main_queue(), ^{
        [self requestCompletePreprocessor];
        [self requestCompleteFilter];
        YTKRequest *strongSelf = self;
        [strongSelf.delegate requestFinished:strongSelf];
        if (strongSelf.successCompletionBlock) {
            strongSelf.successCompletionBlock(strongSelf);
        }
        [strongSelf clearCompletionBlock];
    });
}
这里进行了三步操作
  • 先判断ignoreCache属性是否为YES, 如果为YES, 表明是忽略缓存, 那么直接将所有缓存变量置空, 并调用父类的start方法.

  • 判断是否指定了resumableDownloadPath路径, 如果制定了路径, 说明是下载任务, 下载任务是不做缓存的.

  • 加载缓存, 如果加载成功, 则拿到缓存数据, 走finish代理, 已经成功block回调, 如果加载失败, 则调父类的start方法.

加载缓存成功之后, 在主线程调用requestCompletePreprocessor方法, 这个方法内部会将数据缓存起来:

- (void)saveResponseDataToCacheFile:(NSData *)data {
    if ([self cacheTimeInSeconds] > 0 && ![self isDataFromCache]) {
        if (data != nil) {
            @try {
                // New data will always overwrite old data.
                [data writeToFile:[self cacheFilePath] atomically:YES];

                YTKCacheMetadata *metadata = [[YTKCacheMetadata alloc] init];
                metadata.version = [self cacheVersion];
                metadata.sensitiveDataString = ((NSObject *)[self cacheSensitiveData]).description;
                metadata.stringEncoding = [YTKNetworkUtils stringEncodingWithRequest:self];
                metadata.creationDate = [NSDate date];
                metadata.appVersionString = [YTKNetworkUtils appVersionString];
                [NSKeyedArchiver archiveRootObject:metadata toFile:[self cacheMetadataFilePath]];
            } @catch (NSException *exception) {
                YTKLog(@"Save cache failed, reason = %@", exception.reason);
            }
        }
    }
}

既要存储数据(写文件的方式存储), 也要存储元数据, 元数据中包含了缓存的版本, 编码方式,创建时间, 等等, 元数据用归档的方式存储.

基本的关系图如下图所示:
YTKNetwork各类关系图
YTKNetwork批量请求(YTKBatchRequest)链式请求(YTKChainRequest)
- YTKBatchRequest 批量请求

代码实现其实并不复杂
YTKBatchRequest是将一组YTKRequest打包初始化的

- (instancetype)initWithRequestArray:(NSArray *)requestArray {
    self = [super init];
    if (self) {
        _requestArray = [requestArray copy];
        _finishedCount = 0;
        for (YTKRequest * req in _requestArray) {
            if (![req isKindOfClass:[YTKRequest class]]) {
                YTKLog(@"Error, request item must be YTKRequest instance.");
                return nil;
            }
        }
    }
    return self;
}

然后就是开始批处理请求, 和终止批处理请求, 代码实现逻辑也并不复杂, 就是遍历请求数组, 然后每一个request开始发起请求或者取消请求, 代码如下:

- (void)start {
    if (_finishedCount > 0) {
        YTKLog(@"Error! Batch request has already started.");
        return;
    }
    _failedRequest = nil;
    [[YTKBatchRequestAgent sharedAgent] addBatchRequest:self];
    [self toggleAccessoriesWillStartCallBack];
    for (YTKRequest * req in _requestArray) {
        req.delegate = self;
        [req clearCompletionBlock];
        [req start];
    }
}

- (void)stop {
    [self toggleAccessoriesWillStopCallBack];
    _delegate = nil;
    [self clearRequest];
    [self toggleAccessoriesDidStopCallBack];
    [[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
}

上面两个Api也可以直接调用
YTKBatchRequest 数组里面每一个YTKRequest 请求的或成功, 或失败, 都会走

- (void)requestFinished:(YTKRequest *)request

以及

- (void)requestFailed:(YTKRequest *)request

这两个代理回调, 代码如下:

- (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];
        [[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
    }
}

- (void)requestFailed:(YTKRequest *)request {
    _failedRequest = request;
    [self toggleAccessoriesWillStopCallBack];
    // Stop
    for (YTKRequest *req in _requestArray) {
        [req stop];
    }
    // Callback
    if ([_delegate respondsToSelector:@selector(batchRequestFailed:)]) {
        [_delegate batchRequestFailed:self];
    }
    if (_failureCompletionBlock) {
        _failureCompletionBlock(self);
    }
    // Clear
    [self clearCompletionBlock];

    [self toggleAccessoriesDidStopCallBack];
    [[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
}

每单个YTKRequest 请求结果回来都会走上面两个代理方法

  • 如果走了成功的代理回调, 那么 _finishedCount就会自增.
    _finishedCount变量的值等于请求数组_requestArraycount值时, 表明所有请求都成功了, 此时调用batchRequestFinished:代理方法以及_successCompletionBlock成功的block回调

  • 如果走了失败的代理回调, 只要有一个请求失败, 所有请求都取消:

    for (YTKRequest *req in _requestArray) {
        [req stop];
    }

然后调用batchRequestFailed:代理方法, 以及_failureCompletionBlock失败的代理回调

- YTKChainRequest 链式请求
  • 通过- (void)addRequest:(YTKBaseRequest *)request callback:(YTKChainCallback)callback方法, 将request请求和callback回调方法都用数组保存起来
- (void)addRequest:(YTKBaseRequest *)request callback:(YTKChainCallback)callback {
    [_requestArray addObject:request];
    if (callback != nil) {
        [_requestCallbackArray addObject:callback];
    } else {
        [_requestCallbackArray addObject:_emptyCallback];
    }
}
  • 在调用start内部会调用startNextRequest方法:
- (BOOL)startNextRequest {
    if (_nextRequestIndex < [_requestArray count]) {
        YTKBaseRequest *request = _requestArray[_nextRequestIndex];
        _nextRequestIndex++;
        request.delegate = self;
        [request clearCompletionBlock];
        [request start];
        return YES;
    } else {
        return NO;
    }
}

在这个方法中, 会根据数组角标取出当前的request对象, 发起请求[request start];, 然后会走- (void)requestFinished:(YTKBaseRequest *)request代理回调, 在代理方法中,又会调用startNextRequest方法, 形成了一个递归调用, 直到startNextRequest方法返回NO.

  • 此时, 整个链式调用完毕, 走chainRequestFinished:代理回调方法:
- (void)requestFinished:(YTKBaseRequest *)request {
    NSUInteger currentRequestIndex = _nextRequestIndex - 1;
    YTKChainCallback callback = _requestCallbackArray[currentRequestIndex];
    callback(self, request);
    if (![self startNextRequest]) {
        [self toggleAccessoriesWillStopCallBack];
        if ([_delegate respondsToSelector:@selector(chainRequestFinished:)]) {
            [_delegate chainRequestFinished:self];
            [[YTKChainRequestAgent sharedAgent] removeChainRequest:self];
        }
        [self toggleAccessoriesDidStopCallBack];
    }
}

以上就是我对YTKNetwork源码的分析
YTKNetwork源码

PS. 本人有若干成套学习视频, 包含Java, 数据结构与算法, iOS, 安卓, python, flutter等等, 如有需要, 联系微信tsaievan.

你可能感兴趣的:(离散型网络框架YTKNetwork源码分析)