iOS网络框架AFNetworking3.1.0底层源码解读
2016-12-20 17:49
656 查看
AFNetworking基本是iOS开发中的网络第三方库标配,本文基于AFNetworking3.1.0版本。咱们主要从使用的角度来介绍AFNetworking的发起Get请求的过程,偏重于解读过程,解读当你使用AFNetworking发起一个Get请求的时候,AFNetworking内部的处理过程。而不是对AFNetworking源代码的各个类的代码进行深入解析,在源码深度解析方面,网络上已经有很多不错的文章,在文章的末尾我会给出参考链接。
注:这篇是详解不是使用方式,使用方式去这里找:http://blog.csdn.net/surpassblack/article/details/53764742
发起请求流程图get为例
AFNetworking发起一个Get请求的流程图,大概可以分为这几个步骤,我会逐个解读这个流程。
Get请求流程图
1.AFHTTPSessionManager发起Get请求
发起Get请求
这个方法是AFNetworking的Get请求的起点,其他Get请求的方法也都是直接或者间接调用这个方法来发起Get请求。这个方法的代码量很少也很直观,就是调用其他方法生成NSURLSessionDataTask对象的实例,然后调用NSURLSessionDataTask的resume方法发起请求。
2.创建NSURLSessionDataTask
创建NSURLSessionDataTask
这个方法是创建NSURLSessionDataTask对象实例并返回这个实例。首先创建一个NSMutableURLRequest对象的实例,然后配置。之后是使用NSMutableURLRequest对象的实例创建NSURLSessionDataTask对象实例,然后配置,可以选择性地传入各类Block回调,用于监听网络请求的进度比如上传进度,下载进度,请求成功,请求失败。
3.配置NSMutableURLRequest对象
配置NSMutableURLRequest对象
在这个方法中先使用了url创建了一个NSMutableURLRequest对象的实例,并且设置了HTTPMethod为Get方法(如果是Post方法,那么这里就是设置Post方法,以此类推)然后使用KVC的方法设置了NSMutableURLRequest的一些属性。
配置NSMutableURLRequest对象
先设置HTTP header,之后格式化请求参数,设置参数的编码类型。这个是这个方法的基本操作流程。对于Get操作来说,参数是直接拼接在请求地址后面。
4.配置NSURLSessionDataTask对象
配置NSURLSessionDataTask对象
之后配置NSMutableURLRequest对象就需要配置NSURLSessionDataTask对象了。主要分为2个步骤,第一个步骤是创建NSURLSessionDataTask对象实例,第二个步骤是给NSURLSessionDataTask对象实例设置Delegate。用于实时了解网络请求的过程。
给NSURLSessionDataTask对象实例设置Delegate
AFN的代理统一使用AFURLSessionManagerTaskDelegate对象来管理,使用AFURLSessionManagerTaskDelegate对象来接管NSURLSessionTask网络请求过程中的回调,然后再传入AFN内部进行管理。
如代码所示AFURLSessionManagerTaskDelegate接管了NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate的各种回调,然后做内部处理。这也是第三方网络请求框架的重点,让网络请求更加易用,好用。
通过NSURLSessionTask的taskIdentifier标识符对delegate进行管理,只要是用于识别该NSURLSessionTask的代理
NSURLSessionTask设置进度回调
设置各类回调Block,给NSURLSessionTask使用KVO进行各种过程进度监听。
监听NSURLSessionTask被挂起和恢复的通知
当NSURLSessionTask创建和配置完毕之后,它并不会主动执行,而是需要我们主动调用resume方法,NSURLSessionTask才会开始执行。
网络请求回调
NSURLSessionDelegate方法
NSURLSessionTaskDelegate方法
AFN里面有关NSURLSessionDelegate的回调方法非常的多,这里我们只调和NSURLSessionTask相关的部分方法和KVO处理来进行说明,其他的大家可以参考源码细看。
KVO监听请求过程
收到响应数据
请求完成
对于我们的Get请求来说,我们最关注的莫过于关注请求过程进度,收到响应数据和请求完成这2个回调。
因为在配置NSURLSessionDataTask对象的时候我们有给NSURLSessionTask做了一系列配置,那么当NSURLSessionDataTask任务完成之后,我们需要将该NSURLSessionDataTask的一系列配置全部清理掉。
这个是配置过程
那么对应的清理过程是这样的,就是设置过程中做了什么,在清理过程中就需要去掉什么。
cleanUpProgressForTask
removeNotificationObserverForTask
关于Post请求
请求序列化方法
如果是Post请求,那么请求参数是没有拼接在URL上面,而是放在body上,这个是Post和Get请求的最大区别了,其他过程和Get请求并没有太多区别。
关于HTTPS请求
HTTPS认证1
HTTPS认证2
如果是HTTPS请求的话,那么会先走上面的2个代理方法进行HTTPS认证,之后继续其他操作。
总结
AFN发起Get请求主要分为以下步骤:
1.创建NSURLSessionDataTask
2.配置NSURLSessionDataTask
3.设置NSURLSessionDataTask的Delegate
4.调用NSURLSessionDataTask的resume方法开始请求
5.在Delegate的方法里面处理网络请求的各个过程
6.清理NSURLSessionDataTask的配置
其实也就是使用NSURLSessionDataTask的步骤,AFN在这几个步骤加了一些封装,让整个请求过程更加好用,易用。
这篇文章是我在简书上看到的,为了方便自己记忆理解又整理了一下。
原文链接:http://www.jianshu.com/p/c36159094e24
这是一些网络请求的讲解的链接
http://blog.cnbang.net/tech/2320/
http://blog.cnbang.net/tech/2371/
http://blog.cnbang.net/tech/2416/
http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html
http://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html
http://bugly.qq.com/bbs/forum.php?
http://www.guokr.com/post/114121/mod=viewthread&tid=417&fromuid=6
http://www.guokr.com/post/116169/
http://www.guokr.com/blog/148613/
http://www.cnblogs.com/hyddd/archive/2009/01/07/1371292.html
http://www.cnblogs.com/polobymulberry/p/5140806.html
https://github.com/AFNetworking/AFNetworking/tree/3.1.0
注:这篇是详解不是使用方式,使用方式去这里找:http://blog.csdn.net/surpassblack/article/details/53764742
发起请求流程图get为例
AFNetworking发起一个Get请求的流程图,大概可以分为这几个步骤,我会逐个解读这个流程。
Get请求流程图
1.AFHTTPSessionManager发起Get请求
发起Get请求
这个方法是AFNetworking的Get请求的起点,其他Get请求的方法也都是直接或者间接调用这个方法来发起Get请求。这个方法的代码量很少也很直观,就是调用其他方法生成NSURLSessionDataTask对象的实例,然后调用NSURLSessionDataTask的resume方法发起请求。
2.创建NSURLSessionDataTask
创建NSURLSessionDataTask
这个方法是创建NSURLSessionDataTask对象实例并返回这个实例。首先创建一个NSMutableURLRequest对象的实例,然后配置。之后是使用NSMutableURLRequest对象的实例创建NSURLSessionDataTask对象实例,然后配置,可以选择性地传入各类Block回调,用于监听网络请求的进度比如上传进度,下载进度,请求成功,请求失败。
3.配置NSMutableURLRequest对象
配置NSMutableURLRequest对象
在这个方法中先使用了url创建了一个NSMutableURLRequest对象的实例,并且设置了HTTPMethod为Get方法(如果是Post方法,那么这里就是设置Post方法,以此类推)然后使用KVC的方法设置了NSMutableURLRequest的一些属性。
//设置NSMutableURLRequest的属性 static NSArray * AFHTTPRequestSerializerObservedKeyPaths() { static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //allowsCellularAccess 允许使用数据流量 //cachePolicy 缓存策略 //HTTPShouldHandleCookies 处理Cookie //HTTPShouldUsePipelining 批量请求 //networkServiceType 网络状态 //timeoutInterval 超时 _AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))]; }); return _AFHTTPRequestSerializerObservedKeyPaths; }
配置NSMutableURLRequest对象
先设置HTTP header,之后格式化请求参数,设置参数的编码类型。这个是这个方法的基本操作流程。对于Get操作来说,参数是直接拼接在请求地址后面。
4.配置NSURLSessionDataTask对象
配置NSURLSessionDataTask对象
之后配置NSMutableURLRequest对象就需要配置NSURLSessionDataTask对象了。主要分为2个步骤,第一个步骤是创建NSURLSessionDataTask对象实例,第二个步骤是给NSURLSessionDataTask对象实例设置Delegate。用于实时了解网络请求的过程。
给NSURLSessionDataTask对象实例设置Delegate
AFN的代理统一使用AFURLSessionManagerTaskDelegate对象来管理,使用AFURLSessionManagerTaskDelegate对象来接管NSURLSessionTask网络请求过程中的回调,然后再传入AFN内部进行管理。
@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
如代码所示AFURLSessionManagerTaskDelegate接管了NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate的各种回调,然后做内部处理。这也是第三方网络请求框架的重点,让网络请求更加易用,好用。
//通过task的标识符管理代理 - (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate forTask:(NSURLSessionTask *)task { NSParameterAssert(task); NSParameterAssert(delegate); [self.lock lock]; self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate; [delegate setupProgressForTask:task]; [self addNotificationObserverForTask:task]; [self.lock unlock]; }
通过NSURLSessionTask的taskIdentifier标识符对delegate进行管理,只要是用于识别该NSURLSessionTask的代理
NSURLSessionTask设置进度回调
设置各类回调Block,给NSURLSessionTask使用KVO进行各种过程进度监听。
//给task添加暂停和恢复的通知 - (void)addNotificationObserverForTask:(NSURLSessionTask *)task { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task]; }
监听NSURLSessionTask被挂起和恢复的通知
- (NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(id)parameters progress:(void (^)(NSProgress * _Nonnull))downloadProgress success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure { NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:downloadProgress success:success failure:failure]; [dataTask resume]; return dataTask; }
当NSURLSessionTask创建和配置完毕之后,它并不会主动执行,而是需要我们主动调用resume方法,NSURLSessionTask才会开始执行。
网络请求回调
NSURLSessionDelegate方法
NSURLSessionTaskDelegate方法
AFN里面有关NSURLSessionDelegate的回调方法非常的多,这里我们只调和NSURLSessionTask相关的部分方法和KVO处理来进行说明,其他的大家可以参考源码细看。
KVO监听请求过程
收到响应数据
请求完成
对于我们的Get请求来说,我们最关注的莫过于关注请求过程进度,收到响应数据和请求完成这2个回调。
//KVO监听的属性值发生变化 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) { if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) { NSLog(@"countOfBytesReceived"); //这个是在Get请求下,网络响应过程中已经收到的数据量 self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//已经收到 } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) { NSLog(@"countOfBytesExpectedToReceive"); //这个是在Get请求下,网络响应过程中期待收到的数据量 self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//期待收到 } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) { NSLog(@"countOfBytesSent"); self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//已经发送 } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) { NSLog(@"countOfBytesExpectedToSend"); self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//期待发送 } } else if ([object isEqual:self.downloadProgress]) { //下载进度变化 if (self.downloadProgressBlock) { self.downloadProgressBlock(object); } } else if ([object isEqual:self.uploadProgress]) { //上传进度变化 if (self.uploadProgressBlock) { self.uploadProgressBlock(object); } } }
//收到请求响应 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { NSLog(@"收到请求响应"); NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;//允许继续加载 //是否有收到请求响应的回调Block if (self.dataTaskDidReceiveResponse) { //若有调用该Block disposition = self.dataTaskDidReceiveResponse(session, dataTask, response); } //是否有请求响应完成的回调Block if (completionHandler) { //若有调用该Block completionHandler(disposition); } }
//请求完成 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { NSLog(@"请求完成"); //取出该NSURLSessionTask的代理对象 AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task]; // delegate may be nil when completing a task in the background if (delegate) { //若是该代理对象存在,那么将对应数据转给该代理对象处理 [delegate URLSession:session task:task didCompleteWithError:error]; //NSURLSessionTask任务完成之后,移除该NSURLSessionTask的代理对象 [self removeDelegateForTask:task]; } //是否有请求完成的回调Block if (self.taskDidComplete) { //若有调用改Block self.taskDidComplete(session, task, error); } }
因为在配置NSURLSessionDataTask对象的时候我们有给NSURLSessionTask做了一系列配置,那么当NSURLSessionDataTask任务完成之后,我们需要将该NSURLSessionDataTask的一系列配置全部清理掉。
这个是配置过程
//通过task的标识符管理代理 - (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate forTask:(NSURLSessionTask *)task { NSParameterAssert(task); NSParameterAssert(delegate); [self.lock lock]; self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate; [delegate setupProgressForTask:task]; [self addNotificationObserverForTask:task]; [self.lock unlock]; }
那么对应的清理过程是这样的,就是设置过程中做了什么,在清理过程中就需要去掉什么。
//给task移除delegate - (void)removeDelegateForTask:(NSURLSessionTask *)task { NSParameterAssert(task); AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task]; [self.lock lock]; [delegate cleanUpProgressForTask:task]; [self removeNotificationObserverForTask:task]; [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)]; [self.lock unlock]; }
cleanUpProgressForTask
removeNotificationObserverForTask
关于Post请求
请求序列化方法
#pragma mark - AFURLRequestSerialization //设置Header和请求参数 - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(request); NSMutableURLRequest *mutableRequest = [request mutableCopy]; [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { //判断header的field是否存在,如果不存在则设置,存在则跳过 if (![request valueForHTTPHeaderField:field]) { //设置 header [mutableRequest setValue:value forHTTPHeaderField:field]; } }]; NSString *query = nil; if (parameters) { //用传进来的自定义block格式化请求参数 if (self.queryStringSerialization) { NSError *serializationError; query = self.queryStringSerialization(request, parameters, &serializationError); if (serializationError) { if (error) { *error = serializationError; } return nil; } } else { switch (self.queryStringSerializationStyle) { case AFHTTPRequestQueryStringDefaultStyle: //默认的格式化方式 query = AFQueryStringFromParameters(parameters); break; } } } //判断是否是GET/HEAD/DELETE方法, 对于GET/HEAD/DELETE方法,把参数加到URL后面 if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { //判断是否有参数 if (query && query.length > 0) { //拼接请求参数 NSLog(@"query-->%@",query); mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]]; } } else { // #2864: an empty string is a valid x-www-form-urlencoded payload if (!query) { query = @""; } //参数带在body上,大多是POST PUT if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { //设置Content-Type HTTP头,告诉服务端body的参数编码类型 [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; } [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]]; } return mutableRequest; }
如果是Post请求,那么请求参数是没有拼接在URL上面,而是放在body上,这个是Post和Get请求的最大区别了,其他过程和Get请求并没有太多区别。
关于HTTPS请求
HTTPS认证1
HTTPS认证2
//Http认证处理 //认证处理 /* *http://www.cnblogs.com/polobymulberry/p/5140806.html *web服务器接收到客户端请求时,有时候需要先验证客户端是否为正常用户,再决定是够返回真实数据。 *这种情况称之为服务端要求客户端接收挑战(NSURLAuthenticationChallenge *challenge)。 *接收到挑战后, *客户端要根据服务端传来的challenge来生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential *(disposition指定应对这个挑战的方法,而credential是客户端生成的挑战证书,注意只有challenge中认证方法为NSURLAuthenticationMethodServerTrust的时候,才需要生成挑战证书)。 *最后调用completionHandler回应服务器端的挑战。 */ - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { //NSURLAuthenticationChallenge 挑战处理类型为 默认 /* *NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理 *NSURLSessionAuthChallengeUseCredential:使用指定的证书 *NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战 */ NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __block NSURLCredential *credential = nil; //自定义方法,用来如何应对服务器端的认证挑战 if (self.sessionDidReceiveAuthenticationChallenge) { disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential); } else { //服务端要求客户端提供证书 if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { //客户端评估服务端的安全性 if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { //客户端产生证书 credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; if (credential) { //使用指定的证书 disposition = NSURLSessionAuthChallengeUseCredential; } else { //默认处理 disposition = NSURLSessionAuthChallengePerformDefaultHandling; } } else { //不处理服务端的认证要求 disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } } if (completionHandler) { completionHandler(disposition, credential); } } //如果没有实现方法 /* *- (void)URLSession:(NSURLSession *)session *didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge *completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler */ //那么URLSession会调用下面的方法进入认证处理 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __block NSURLCredential *credential = nil; if (self.taskDidReceiveAuthenticationChallenge) { disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential); } else { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { disposition = NSURLSessionAuthChallengeUseCredential; credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } } if (completionHandler) { completionHandler(disposition, credential); } }
如果是HTTPS请求的话,那么会先走上面的2个代理方法进行HTTPS认证,之后继续其他操作。
总结
AFN发起Get请求主要分为以下步骤:
1.创建NSURLSessionDataTask
2.配置NSURLSessionDataTask
3.设置NSURLSessionDataTask的Delegate
4.调用NSURLSessionDataTask的resume方法开始请求
5.在Delegate的方法里面处理网络请求的各个过程
6.清理NSURLSessionDataTask的配置
其实也就是使用NSURLSessionDataTask的步骤,AFN在这几个步骤加了一些封装,让整个请求过程更加好用,易用。
这篇文章是我在简书上看到的,为了方便自己记忆理解又整理了一下。
原文链接:http://www.jianshu.com/p/c36159094e24
这是一些网络请求的讲解的链接
http://blog.cnbang.net/tech/2320/
http://blog.cnbang.net/tech/2371/
http://blog.cnbang.net/tech/2416/
http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html
http://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html
http://bugly.qq.com/bbs/forum.php?
http://www.guokr.com/post/114121/mod=viewthread&tid=417&fromuid=6
http://www.guokr.com/post/116169/
http://www.guokr.com/blog/148613/
http://www.cnblogs.com/hyddd/archive/2009/01/07/1371292.html
http://www.cnblogs.com/polobymulberry/p/5140806.html
https://github.com/AFNetworking/AFNetworking/tree/3.1.0
相关文章推荐
- android wifi 无线调试
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 第三方推送已死
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 不可修补的 iOS 漏洞可能导致 iPhone 4s 到 iPhone X 永久越狱
- iOS 12.4 系统遭黑客破解,漏洞危及数百万用户
- 每日安全资讯:NSO,一家专业入侵 iPhone 的神秘公司
- [转][源代码]Comex公布JailbreakMe 3.0源代码
- 虚拟化基础架构Windows 2008篇之10-使用WDS安装Windows 7
- 理解vSphere虚拟交换机中的VLAN类型
- 拨号网络的简单知识
- 菜鸟必看网络名词
- 通晓网络测试常用命令
- 网络路由技术及运用2
- IP网络路由技术
- 网络防火墙的设置技巧
- 网络管理之IP地址篇