首先

  关于网络层最先可能想到的是AFNetworking,或者Swift中的Alamofire,直接使用起来也特别的简单,但是稍复杂的项目如果直接使用就显得不够用了,首先第三方耦合不说,就光散落在各处的请求回调就难以后期维护,所以一般会有针对性的再次封装,往往初期可能业务相对简单,考虑的方面较少,后期业务增加可能需要对网络层进行重构,一个好的架构也一定是和业务层面紧密相连的,随业务的增长不断健壮的。

最近也是看了YTKNetwork的源码和相关博客,站在前辈的肩膀上写下一些自己关于网络层的解读。

与业务层对接方式

常见的与业务层对接方式两种:

  • 集约型:

    最典型就属于上面说的AFNetworking、Alamofire,发起网络请求都集中在一个类上,请求回调通过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]  网络请求

[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  [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()

@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()

@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

最后

最近也是看得比写代码多,大都一些源码和博客,种类也比较泛,读懂作者的思路还是收获颇多,往往有时候会要上好几遍,每一遍都有些新的收获,贵在坚持了。

源码解析之–网络层YTKNetwork的更多相关文章

  1. iOS网络请求-AFNetworking源码解析

    趁着端午节日,自己没有什么过多的安排,准备花4-5天左右,针对网络请求源码AFNetworking和YTKNetwork进行解析以及这两年多iOS实际开发经验(其实YTKNetwork也是对AFNet ...

  2. Java生鲜电商平台-SpringCloud微服务架构中网络请求性能优化与源码解析

    Java生鲜电商平台-SpringCloud微服务架构中网络请求性能优化与源码解析 说明:Java生鲜电商平台中,由于服务进行了拆分,很多的业务服务导致了请求的网络延迟与性能消耗,对应的这些问题,我们 ...

  3. [源码解析] 深度学习流水线并行Gpipe(1)---流水线基本实现

    [源码解析] 深度学习流水线并行Gpipe(1)---流水线基本实现 目录 [源码解析] 深度学习流水线并行Gpipe(1)---流水线基本实现 0x00 摘要 0x01 概述 1.1 什么是GPip ...

  4. [源码解析] 深度学习流水线并行 GPipe(3) ----重计算

    [源码解析] 深度学习流水线并行 GPipe(3) ----重计算 目录 [源码解析] 深度学习流水线并行 GPipe(3) ----重计算 0x00 摘要 0x01 概述 1.1 前文回顾 1.2 ...

  5. [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (2)

    [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (2) 目录 [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (2) 0x00 摘要 0x01 总体流程 ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  7. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  8. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  9. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

随机推荐

  1. matlab操作之--读取指定文件夹下的“指定格式”文件

    %% 正负样本所在folder fext='*.png';%要读取的文件格式 positiveFolder='F:\课题\Crater detection\machingLearning\Positi ...

  2. bzoj 1002 [FJOI2007]轮状病毒 高精度&&找规律&&基尔霍夫矩阵

    1002: [FJOI2007]轮状病毒 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 2234  Solved: 1227[Submit][Statu ...

  3. iOS:关于获取网络类型和运营商信息

    目录 1. 获取运营商网络类型 2. 获取运营商信息 返回目录 1. 获取运营商网络类型 Apple的Reachability Sample看起来不错,但是只可以判断是否连接到互联网和是否连接Wifi ...

  4. CPU 定位高

    流程:把线程dump出来,然后分析 1:Threaddump的方法: kill -3 pid     jstack -l pid     jvisualvm中来thread dump 2:找到导致cp ...

  5. keil 工程中多文件编译时全局变量怎么引用

    由于代码较多时,为了代码的工整以及易读性,往往将代码拆分成模块,并书写头文件.但keil中定义全局变量往往是一件头疼的事情. (1)xx.h文件中基本书写的是管脚定义和函数声明,全局变量不能定义在头文 ...

  6. C语言嵌入式系统编程修炼之四:屏幕操作

    汉字处理 现在要解决的问题是,嵌入式系统中经常要使用的并非是完整的汉字库,往往只是需要提供数量有限的汉字供必要的显示功能.例如,一个微波炉的LCD上没有必要提供显示"电子邮件"的功 ...

  7. leetcode面试准备:Multiply Strings

    1 题目 Given two numbers represented as strings, return multiplication of the numbers as a string. Not ...

  8. 14.8.4 Moving or Copying InnoDB Tables to Another Machine 移动或者拷贝 InnoDB 表到另外机器

    14.8.4 Moving or Copying InnoDB Tables to Another Machine 移动或者拷贝 InnoDB 表到另外机器 这个章节描述技术关于移动或者复制一些或者所 ...

  9. WordPress Design Approval System插件‘step’参数跨站脚本漏洞

    漏洞名称: WordPress Design Approval System插件‘step’参数跨站脚本漏洞 CNNVD编号: CNNVD-201309-084 发布时间: 2013-09-11 更新 ...

  10. Quality in the Test Automation Review Process and Design Review Template

    About this document Prerequisite knowledge/experience: Software Testing, Test Automation Applicable ...