美文网首页
YTKNetwork源码解析

YTKNetwork源码解析

作者: p_peng | 来源:发表于2019-06-28 09:50 被阅读0次

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

YTKNetwork 的基本的思想是把每一个网络请求封装成对象。所以使用 YTKNetwork,你的每一个请求都需要继承 YTKRequest 类,通过覆盖父类的一些方法来构造指定的网络请求。

YTKBaseRequest

YTKRequest继承自YTKBaseRequest,首先来看下YTKBaseRequest这个类,下面是请求网络的代码

#pragma mark - Request Action

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

- (void)stop {
    [self toggleAccessoriesWillStopCallBack];
    self.delegate = nil;
    [[YTKNetworkAgent sharedAgent] cancelRequest:self];
    [self toggleAccessoriesDidStopCallBack];
}

- (void)startWithCompletionBlockWithSuccess:(YTKRequestCompletionBlock)success
                                    failure:(YTKRequestCompletionBlock)failure {
    [self setCompletionBlockWithSuccess:success failure:failure];
    [self start];
}

Accessory是附属物的意思,类似hook的意思,可以在每个流程点调用对应方法。

除去Accessory相关的代码,就只剩下YTKNetworkAgent的方法调用了,有addRequest:cancelRequest:两个实例方法。我们进入这里去看看。

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

    NSError * __autoreleasing requestSerializationError = nil;

    // 构建requestTask(NSURLSessionTask)
    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");

    // 优先级设置,只在iOS 8以上有用,所以使用respondsToSelector做判断
    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;
        }
    }

    YTKLog(@"Add request: %@", NSStringFromClass([request class]));
    // 缓存请求,并开始请求
    [self addRequestToRecord:request];
    [request.requestTask resume];
}

这个方法主要分为三个部分:

  1. 构造NSURLSessionTask,这也是iOS自带的网络请求库。
  2. 设置task的优先级,这个看注释是只在iOS8以上才有。
  3. 缓存请求,调用[task resume],发起网络请求。

我们再进入第一个部分详细看下

    // 构建requestTask(NSURLSessionTask)
    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];
    }

如果自定义的request实现了buildCustomUrlRequest方法,直接使用manager创建task。如果没有实现,使用AgentsessionTaskForRequest:error:方法创建task

- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
    YTKRequestMethod method = [request requestMethod];
    NSString *url = [self buildRequestUrl:request];
    id param = request.requestArgument;
    AFConstructingBlock constructingBlock = [request constructingBodyBlock];
    AFHTTPRequestSerializer *requestSerializer = [self requestSerializerForRequest:request];

    switch (method) {
        case YTKRequestMethodGET:
            if (request.resumableDownloadPath) {
                return [self downloadTaskWithDownloadPath:request.resumableDownloadPath requestSerializer:requestSerializer URLString:url parameters:param progress:request.resumableDownloadProgressBlock error:error];
            } else {
                return [self dataTaskWithHTTPMethod:@"GET" requestSerializer:requestSerializer URLString:url parameters:param error:error];
            }
        case YTKRequestMethodPOST:
            return [self dataTaskWithHTTPMethod:@"POST" requestSerializer:requestSerializer URLString:url parameters:param constructingBodyWithBlock:constructingBlock error:error];
        case YTKRequestMethodHEAD:
            return [self dataTaskWithHTTPMethod:@"HEAD" requestSerializer:requestSerializer URLString:url parameters:param error:error];
        case YTKRequestMethodPUT:
            return [self dataTaskWithHTTPMethod:@"PUT" requestSerializer:requestSerializer URLString:url parameters:param error:error];
        case YTKRequestMethodDELETE:
            return [self dataTaskWithHTTPMethod:@"DELETE" requestSerializer:requestSerializer URLString:url parameters:param error:error];
        case YTKRequestMethodPATCH:
            return [self dataTaskWithHTTPMethod:@"PATCH" requestSerializer:requestSerializer URLString:url parameters:param error:error];
    }
}

根据请求的方法,如果是GET方法,而且实现了resumableDownloadPath,那么创建一个NSURLSessionDownloadTask,其他则最终会进入到下面这个方法

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                               requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                       constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
                                           error:(NSError * _Nullable __autoreleasing *)error {
    NSMutableURLRequest *request = nil;

    if (block) {
        request = [requestSerializer multipartFormRequestWithMethod:method URLString:URLString parameters:parameters constructingBodyWithBlock:block error:error];
    } else {
        request = [requestSerializer requestWithMethod:method URLString:URLString parameters:parameters error:error];
    }

    __block NSURLSessionDataTask *dataTask = nil;
    dataTask = [_manager dataTaskWithRequest:request
                           completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *_error) {
                               [self handleRequestResult:dataTask responseObject:responseObject error:_error];
                           }];

    return dataTask;
}

这里参数中有个block,是用与上传文件的请求的。根据这个参数,会调用requestSerializer不同的方法,最后调用_manager的方法创建最终的dataTask,这些都是AFNetworking提供的方法。回调方式是block,调用Agent中的handleRequestResult:responseObject:error:方法。

开始请求之前,有一个请求缓存操作,addRequestToRecord:,将request对象缓存到一个Map中,key是上面创建的task的taskIdentifier。这里还是用了多线程锁对这个缓存进行上锁,使用的是pthread_mutex_lock。这个缓存有什么用?我们还是继续往下看,后面会揭晓。

#define Lock() pthread_mutex_lock(&_lock)
#define Unlock() pthread_mutex_unlock(&_lock)

- (void)addRequestToRecord:(YTKBaseRequest *)request {
    Lock();
    _requestsRecord[@(request.requestTask.taskIdentifier)] = request;
    Unlock();
}

我们再看下回调方法

- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {
    Lock();
    YTKBaseRequest *request = _requestsRecord[@(task.taskIdentifier)];
    Unlock();

    // When the request is cancelled and removed from records, the underlying
    // AFNetworking failure callback will still kicks in, resulting in a nil `request`.
    //
    // Here we choose to completely ignore cancelled tasks. Neither success or failure
    // callback will be called.
    if (!request) {
        return;
    }

    YTKLog(@"Finished Request: %@", NSStringFromClass([request class]));

    NSError * __autoreleasing serializationError = nil;
    NSError * __autoreleasing validationError = nil;

    NSError *requestError = nil;
    BOOL succeed = NO;

    request.responseObject = responseObject;
    if ([request.responseObject isKindOfClass:[NSData class]]) {
        request.responseData = responseObject;
        request.responseString = [[NSString alloc] initWithData:responseObject encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];

        switch (request.responseSerializerType) {
            case YTKResponseSerializerTypeHTTP:
                // Default serializer. Do nothing.
                break;
            case YTKResponseSerializerTypeJSON:
                request.responseObject = [self.jsonResponseSerializer responseObjectForResponse:task.response data:request.responseData error:&serializationError];
                request.responseJSONObject = request.responseObject;
                break;
            case YTKResponseSerializerTypeXMLParser:
                request.responseObject = [self.xmlParserResponseSerialzier responseObjectForResponse:task.response data:request.responseData error:&serializationError];
                break;
        }
    }
    if (error) {
        succeed = NO;
        requestError = error;
    } else if (serializationError) {
        succeed = NO;
        requestError = serializationError;
    } else {
        succeed = [self validateResult:request error:&validationError];
        requestError = validationError;
    }

    if (succeed) {
        [self requestDidSucceedWithRequest:request];
    } else {
        [self requestDidFailWithRequest:request error:requestError];
    }

    dispatch_async(dispatch_get_main_queue(), ^{
        [self removeRequestFromRecord:request];
        [request clearCompletionBlock];
    });
}

回调方法里面就用到了上面的缓存,如果请求已经取消,缓存中已经不存在这个request对象,那么回调函数就不用再往下执行了。如果没有取消,继续下面的处理

  1. 首先根据响应的类型responseSerializerType序列化数据,支持了Json和XML两种序列化,其他的直接使用responseObject
  2. 序列化如果也没有报错的话,进行结果的验证,首先是statuscode,从task的response拿到statusCode,如果在200~299之间,那么继续。结果内容的检查只支持Json数据的检查,调用[YTKNetworkUtils validateJSON:json withValidator:validator];进行检查。
  3. 检查通过后通知请求成功,并从Map缓存中移除请求。

从上面可以看出,主要的逻辑是在YTKNetworkAgent这个类里面。

YTKRequest

上面介绍完了YTKBaseRequest,我们回到YTKRequest中来看下。这里主要处理的是网络请求结果的缓存。

重写了开始请求的方法

- (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];
    });
}

对于指定忽略缓存的任务和下载任务,直接进行网络请求,否则进行缓存加载loadCacheWithError:,缓存命中,直接返回,没有的话进行网络请求。

取消请求

取消请求又要回到YTKBaseRequest中来,调用stop方法。最后会进入YTKNetworkAgentcancelRequest:方法。

- (void)stop {
    [self toggleAccessoriesWillStopCallBack];
    self.delegate = nil;
    [[YTKNetworkAgent sharedAgent] cancelRequest:self];
    [self toggleAccessoriesDidStopCallBack];
}

- (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];
}

cancelRequest:方法会对下载任务进行处理,将下载的部分数据进行本地保存,创建下载任务的时候也会根据这个本地数据决定是否进行断点续传。

- (NSURLSessionDownloadTask *)downloadTaskWithDownloadPath:(NSString *)downloadPath
                                         requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
                                                 URLString:(NSString *)URLString
                                                parameters:(id)parameters
                                                  progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
                                                     error:(NSError * _Nullable __autoreleasing *)error {
    // add parameters to URL;
    NSMutableURLRequest *urlRequest = [requestSerializer requestWithMethod:@"GET" URLString:URLString parameters:parameters error:error];

    NSString *downloadTargetPath;
    BOOL isDirectory;
    if(![[NSFileManager defaultManager] fileExistsAtPath:downloadPath isDirectory:&isDirectory]) {
        isDirectory = NO;
    }
    // If targetPath is a directory, use the file name we got from the urlRequest.
    // Make sure downloadTargetPath is always a file, not directory.
    if (isDirectory) {
        NSString *fileName = [urlRequest.URL lastPathComponent];
        downloadTargetPath = [NSString pathWithComponents:@[downloadPath, fileName]];
    } else {
        downloadTargetPath = downloadPath;
    }

    // AFN use `moveItemAtURL` to move downloaded file to target path,
    // this method aborts the move attempt if a file already exist at the path.
    // So we remove the exist file before we start the download task.
    // https://github.com/AFNetworking/AFNetworking/issues/3775
    if ([[NSFileManager defaultManager] fileExistsAtPath:downloadTargetPath]) {
        [[NSFileManager defaultManager] removeItemAtPath:downloadTargetPath error:nil];
    }

    BOOL resumeDataFileExists = [[NSFileManager defaultManager] fileExistsAtPath:[self incompleteDownloadTempPathForDownloadPath:downloadPath].path];
    NSData *data = [NSData dataWithContentsOfURL:[self incompleteDownloadTempPathForDownloadPath:downloadPath]];
    BOOL resumeDataIsValid = [YTKNetworkUtils validateResumeData:data];

    BOOL canBeResumed = resumeDataFileExists && resumeDataIsValid;
    BOOL resumeSucceeded = NO;
    __block NSURLSessionDownloadTask *downloadTask = nil;
    // Try to resume with resumeData.
    // Even though we try to validate the resumeData, this may still fail and raise excecption.
    if (canBeResumed) {
        @try {
            downloadTask = [_manager downloadTaskWithResumeData:data progress:downloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
                return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO];
            } completionHandler:
                            ^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
                                [self handleRequestResult:downloadTask responseObject:filePath error:error];
                            }];
            resumeSucceeded = YES;
        } @catch (NSException *exception) {
            YTKLog(@"Resume download failed, reason = %@", exception.reason);
            resumeSucceeded = NO;
        }
    }
    if (!resumeSucceeded) {
        downloadTask = [_manager downloadTaskWithRequest:urlRequest progress:downloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
            return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO];
        } completionHandler:
                        ^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
                            [self handleRequestResult:downloadTask responseObject:filePath error:error];
                        }];
    }
    return downloadTask;
}

总结

按照YTKNetwork的思想,所有的请求都会继承自YTKRequest,所以有以下几个特点

  1. 默认支持下载任务的断点续传。
  2. YTKRequest的子类只要重写cacheTimeInSeconds方法就可以实现请求结果的缓存。其他的方法都有默认实现。
  3. 支持请求结果Json验证。

设计模式

上面的代码中有很多Accessory相关的,这属于代理模式。除此之外在YTKNetworkConfig中还支持添加两种filter,YTKUrlFilterProtocolYTKCacheDirPathFilterProtocol,这些从设计模式上讲都属于装饰者模式。

如果有哪里写的不清楚或者不对的地方,欢迎大家学习指正。

相关文章

  • ios三方库解析

    YYCache 源码解析 YTKNetwork 源码解析 MJRefresh 源码解析 VVeboTableVie...

  • iOS 一些框架源码解析

    YYCache 源码解析 YTKNetwork源码解析 MJRefresh 源码解析 VVeboTableView...

  • YTKNetwork

    YTKNetwork集成教程以及相关问题思考 源码解析之--YTKNetwork网络层 YTKNetwork源码解...

  • YTKNetWork源码解析

    背景: YTKNetWork是一个开源的第三方网络请求框架,具有比较好的网络请求缓存机制的控制。近期项目中想要采取...

  • YTKNetwork源码解析

    对于iOS开发者来说,就算是没有用过YTKNetwork框架,应该也见过,听过了。它是猿题库技术团队开源的一个网络...

  • YTKNetwork源码解析

    对于大多数app来说,业务量到一定级别,为了可维护性和框架的合理性,通常都需要设计专门的网络层,将业务逻辑中的所有...

  • YTKNetwork源码解析

    YTKNetwork 是猿题库 iOS 研发团队基于 AFNetworking 封装的 iOS 网络库,其实现了一...

  • YTKNetwork源码解析2

    YTKNetwork在GitHub的仓库中有一份高级教程,这篇我们就来看下高级教程中相关部分的源码。 YTKUrl...

  • iOS YTKNetwork源码解析

    我们经常把数据请求和数据处理放到Controller中,导致对AFNetwork的依赖过高,实际上AFNetwor...

  • YTKNetwork 源码分析

    YTKNetworkAnalysis YTKNetwork 源码分析 1. 功能介绍 1.1 YTKNetwork...

网友评论

      本文标题:YTKNetwork源码解析

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