美文网首页iOS 中间件开发iOS基本功
离散型网络框架YTKNetwork源码分析

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

作者: 小冰山口 | 来源:发表于2019-04-23 18:57 被阅读53次
    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<NSString *> *)requestAuthorizationHeaderFieldArray;
    
    ///  添加的请求头信息
    - (nullable NSDictionary<NSString *, NSString *> *)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<YTKRequest *> *)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源码

    相关文章

      网友评论

        本文标题:离散型网络框架YTKNetwork源码分析

        本文链接:https://www.haomeiwen.com/subject/ngivgqtx.html