生成式AI领域的最新成果都在这里!抢 QCon 展区门票 了解详情
写点什么

AFNetworking 的核心 AFURLSessionManager(二)

  • 2019-12-10
  • 本文字数:10993 字

    阅读完需:约 36 分钟

AFNetworking 的核心 AFURLSessionManager(二)

关注仓库,及时获得更新:iOS-Source-Code-Analyze


AFURLSessionManager 绝对可以称得上是 AFNetworking 的核心。


  1. 负责创建和管理 NSURLSession

  2. 管理 NSURLSessionTask

  3. 实现 NSURLSessionDelegate 等协议中的代理方法

  4. 使用 AFURLSessionManagerTaskDelegate 管理进度

  5. 使用 _AFURLSessionTaskSwizzling 调剂方法

  6. 引入 AFSecurityPolicy 保证请求的安全

  7. 引入 AFNetworkReachabilityManager 监控网络状态


我们会在这里着重介绍上面七个功能中的前五个,分析它是如何包装 NSURLSession 以及众多代理方法的。

创建和管理 NSURLSession

在使用 AFURLSessionManager 时,第一件要做的事情一定是初始化:


Objective-C


- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {    self = [super init];    if (!self) {        return nil;    }
if (!configuration) { configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; }
self.sessionConfiguration = configuration;
self.operationQueue = [[NSOperationQueue alloc] init]; self.operationQueue.maxConcurrentOperationCount = 1;
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
self.responseSerializer = [AFJSONResponseSerializer serializer];
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
self.lock = [[NSLock alloc] init]; self.lock.name = AFURLSessionManagerLockName;
#1: 为已有的 task 设置代理, 略
return self;}
复制代码


在初始化方法中,需要完成初始化一些自己持有的实例:


  1. 初始化会话配置(NSURLSessionConfiguration),默认为 defaultSessionConfiguration

  2. 初始化会话(session),并设置会话的代理以及代理队列

  3. 初始化管理响应序列化(AFJSONResponseSerializer),安全认证(AFSecurityPolicy)以及监控网络状态(AFNetworkReachabilityManager)的实例

  4. 初始化保存 data task 的字典(mutableTaskDelegatesKeyedByTaskIdentifier)

管理 NSURLSessionTask

接下来,在获得了 AFURLSessionManager 的实例之后,我们可以通过以下方法创建 NSURLSessionDataTask 的实例:


Objective-C


- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL progress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
...
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request progress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;
...
复制代码


这里省略了一些返回 NSURLSessionTask 的方法,因为这些接口的形式都是差不多的。


我们将以 - [AFURLSessionManager dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:] 方法的实现为例,分析它是如何实例化并返回一个 NSURLSessionTask 的实例的:


Objective-C


- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {
__block NSURLSessionDataTask *dataTask = nil; url_session_manager_create_task_safely(^{ dataTask = [self.session dataTaskWithRequest:request]; });
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;}
复制代码


url_session_manager_create_task_safely 的调用是因为苹果框架中的一个 bug #2093,如果有兴趣可以看一下,在这里就不说明了


  1. 调用 - [NSURLSession dataTaskWithRequest:] 方法传入 NSURLRequest

  2. 调用 - [AFURLSessionManager addDelegateForDataTask:uploadProgress:downloadProgress:completionHandler:] 方法返回一个 AFURLSessionManagerTaskDelegate 对象

  3. completionHandler uploadProgressBlockdownloadProgressBlock 传入该对象并在相应事件发生时进行回调


Objective-C


- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask                uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock              downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock             completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler{    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];    delegate.manager = self;    delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks; [self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock; delegate.downloadProgressBlock = downloadProgressBlock;}
复制代码


在这个方法中同时调用了另一个方法 - [AFURLSessionManager setDelegate:forTask:] 来设置代理:


Objective-C


- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate            forTask:(NSURLSessionTask *)task{
#1: 检查参数, 略
[self.lock lock]; self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate; [delegate setupProgressForTask:task]; [self addNotificationObserverForTask:task]; [self.lock unlock];}
复制代码


正如上面所提到的,AFNRUSessionManager 就是通过字典 mutableTaskDelegatesKeyedByTaskIdentifier 来存储并管理每一个 NSURLSessionTask,它以 taskIdentifier 为键存储 task。


该方法使用 NSLock 来保证不同线程使用 mutableTaskDelegatesKeyedByTaskIdentifier 时,不会出现线程竞争的问题。


同时调用 - setupProgressForTask:,我们会在下面具体介绍这个方法。

实现 NSURLSessionDelegate 等协议中的代理方法

AFURLSessionManager 的头文件中可以看到,它遵循了多个协议,其中包括:


  • NSURLSessionDelegate

  • NSURLSessionTaskDelegate

  • NSURLSessionDataDelegate

  • NSURLSessionDownloadDelegate


它在初始化方法 - [AFURLSessionManager initWithSessionConfiguration:]NSURLSession 的代理指向 self,然后实现这些方法,提供更简洁的 block 的接口:


Objective-C


- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;- (void)setSessionDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential))block;...
复制代码


它为所有的代理协议都提供了对应的 block 接口,方法实现的思路都是相似的,我们以 - [AFNRLSessionManager setSessionDidBecomeInvalidBlock:] 为例。


首先调用 setter 方法,将 block 存入 sessionDidBecomeInvalid 属性中:


Objective-C


- (void)setSessionDidBecomeInvalidBlock:(void (^)(NSURLSession *session, NSError *error))block {    self.sessionDidBecomeInvalid = block;}
复制代码


当代理方法调用时,如果存在对应的 block,会执行对应的 block:


Objective-C


- (void)URLSession:(NSURLSession *)sessiondidBecomeInvalidWithError:(NSError *)error{    if (self.sessionDidBecomeInvalid) {        self.sessionDidBecomeInvalid(session, error);    }
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];}
复制代码


其他相似的接口实现也都差不多,这里直接跳过了。

使用 AFURLSessionManagerTaskDelegate 管理进度

在上面我们提到过 AFURLSessionManagerTaskDelegate 类,它主要为 task 提供进度管理功能,并在 task 结束时回调, 也就是调用在 - [AFURLSessionManager dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:] 等方法中传入的 completionHandler


我们首先分析一下 AFURLSessionManagerTaskDelegate 是如何对进度进行跟踪的:


Objective-C


- (void)setupProgressForTask:(NSURLSessionTask *)task {
#1:设置在上传进度或者下载进度状态改变时的回调
#2:KVO
}
复制代码


该方法的实现有两个部分,一部分是对代理持有的两个属性 uploadProgressdownloadProgress 设置回调


Objective-C


__weak __typeof__(task) weakTask = task;
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;[self.uploadProgress setCancellable:YES];[self.uploadProgress setCancellationHandler:^{ __typeof__(weakTask) strongTask = weakTask; [strongTask cancel];}];[self.uploadProgress setPausable:YES];[self.uploadProgress setPausingHandler:^{ __typeof__(weakTask) strongTask = weakTask; [strongTask suspend];}];if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) { [self.uploadProgress setResumingHandler:^{ __typeof__(weakTask) strongTask = weakTask; [strongTask resume]; }];}
复制代码


这里只有对 uploadProgress 设置回调的代码,设置 downloadProgress 与这里完全相同


主要目的是在对应 NSProgress 的状态改变时,调用 resume suspend 等方法改变 task 的状态。


第二部分是对 task 和 NSProgress 属性进行键值观测:


Objective-C


[task addObserver:self      forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))         options:NSKeyValueObservingOptionNew         context:NULL];[task addObserver:self      forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))         options:NSKeyValueObservingOptionNew         context:NULL];
[task addObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent)) options:NSKeyValueObservingOptionNew context:NULL];[task addObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend)) options:NSKeyValueObservingOptionNew context:NULL];
[self.downloadProgress addObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) options:NSKeyValueObservingOptionNew context:NULL];[self.uploadProgress addObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) options:NSKeyValueObservingOptionNew context:NULL];
复制代码


observeValueForKeypath:ofObject:change:context: 方法中改变进度,并调用 block


Objective-C


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {    if ([object isKindOfClass:[NSURLSessionTask class]]) {        if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {            self.downloadProgress.completedUnitCount = [change[@"new"] longLongValue];        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {            self.downloadProgress.totalUnitCount = [change[@"new"] longLongValue];        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {            self.uploadProgress.completedUnitCount = [change[@"new"] longLongValue];        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {            self.uploadProgress.totalUnitCount = [change[@"new"] 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);        }    }}
复制代码


对象的某些属性改变时更新 NSProgress 对象或使用 block 传递 NSProgress 对象 self.uploadProgressBlock(object)

代理方法 URLSession:task:didCompleteWithError:

在每一个 NSURLSessionTask 结束时,都会在代理方法 URLSession:task:didCompleteWithError: 中:


  1. 调用传入的 completionHander block

  2. 发出 AFNetworkingTaskDidCompleteNotification 通知


Objective-C


- (void)URLSession:(__unused NSURLSession *)session              task:(NSURLSessionTask *)taskdidCompleteWithError:(NSError *)error{    #1:获取数据, 存储 `responseSerializer` 和 `downloadFileURL`
if (error) { #2:在存在错误时调用 `completionHandler` } else { #3:调用 `completionHandler` }}
复制代码


这是整个代理方法的骨架,先看一下最简单的第一部分代码:


Objective-C


__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//Performance Improvement from #2672NSData *data = nil;if (self.mutableData) { data = [self.mutableData copy]; //We no longer need the reference, so nil it out to gain back some memory. self.mutableData = nil;}
if (self.downloadFileURL) { userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;} else if (data) { userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;}
复制代码


这部分代码从 mutableData 中取出了数据,设置了 userInfo


Objective-C


userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{ if (self.completionHandler) { self.completionHandler(task.response, responseObject, error); }
dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo]; });});
复制代码


如果当前 manager 持有 completionGroup 或者 completionQueue 就使用它们。否则会创建一个 dispatch_group_t 并在主线程中调用 completionHandler 并发送通知(在主线程中)。


如果在执行当前 task 时没有遇到错误,那么先对数据进行序列化,然后同样调用 block 并发送通知。


Objective-C


dispatch_async(url_session_manager_processing_queue(), ^{    NSError *serializationError = nil;    responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
if (self.downloadFileURL) { responseObject = self.downloadFileURL; }
if (responseObject) { userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject; }
if (serializationError) { userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError; }
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{ if (self.completionHandler) { self.completionHandler(task.response, responseObject, serializationError); }
dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo]; }); });});
复制代码

代理方法 URLSession:dataTask:didReceiveData:- URLSession:downloadTask:didFinishDownloadingToURL:

这两个代理方法分别会在收到数据或者完成下载对应文件时调用,作用分别是为 mutableData 追加数据和处理下载的文件:


Objective-C


- (void)URLSession:(__unused NSURLSession *)session          dataTask:(__unused NSURLSessionDataTask *)dataTask    didReceiveData:(NSData *)data{    [self.mutableData appendData:data];}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTaskdidFinishDownloadingToURL:(NSURL *)location{ NSError *fileManagerError = nil; self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) { self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location); if (self.downloadFileURL) { [[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError];
if (fileManagerError) { [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo]; } } }}
复制代码

使用 _AFURLSessionTaskSwizzling 调剂方法

_AFURLSessionTaskSwizzling 的唯一功能就是修改 NSURLSessionTaskresumesuspend 方法,使用下面的方法替换原有的实现


Objective-C


- (void)af_resume {    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");    NSURLSessionTaskState state = [self state];    [self af_resume];
if (state != NSURLSessionTaskStateRunning) { [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self]; }}
- (void)af_suspend { NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state"); NSURLSessionTaskState state = [self state]; [self af_suspend];
if (state != NSURLSessionTaskStateSuspended) { [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self]; }}
复制代码


这样做的目的是为了在方法 resume 或者 suspend 被调用时发出通知。


具体方法调剂的过程是在 + load 方法中进行的


load 方法只会在整个文件被引入时调用一次


Objective-C


+ (void)load {    if (NSClassFromString(@"NSURLSessionTask")) {        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];        NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];#pragma GCC diagnostic push#pragma GCC diagnostic ignored "-Wnonnull"        NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];#pragma clang diagnostic pop        IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));        Class currentClass = [localDataTask class];
while (class_getInstanceMethod(currentClass, @selector(resume))) { Class superClass = [currentClass superclass]; IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume))); IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume))); if (classResumeIMP != superclassResumeIMP && originalAFResumeIMP != classResumeIMP) { [self swizzleResumeAndSuspendMethodForClass:currentClass]; } currentClass = [currentClass superclass]; }
[localDataTask cancel]; [session finishTasksAndInvalidate]; }}
复制代码


  1. 首先用 NSClassFromString(@"NSURLSessionTask") 判断当前部署的 iOS 版本是否含有类 NSURLSessionTask

  2. 因为 iOS7 和 iOS8 上对于 NSURLSessionTask 的实现不同,所以会通过 - [NSURLSession dataTaskWithURL:] 方法返回一个 NSURLSessionTask 实例

  3. 取得当前类 _AFURLSessionTaskSwizzling 中的实现 af_resume

  4. 如果当前类 currentClassresume 方法

  5. 真:5

  6. 假:7

  7. 使用 swizzleResumeAndSuspendMethodForClass: 调剂该类的 resumesuspend 方法

  8. currentClass = [currentClass superclass]


这里复杂的实现是为了解决 bug #2702

引入 AFSecurityPolicy 保证请求的安全

AFSecurityPolicyAFNetworking 用来保证 HTTP 请求安全的类,它被 AFURLSessionManager 持有,如果你在 AFURLSessionManager 的实现文件中搜索 self.securityPolicy,你只会得到三条结果:


  1. 初始化 self.securityPolicy = [AFSecurityPolicy defaultPolicy]

  2. 收到连接层的验证请求时

  3. 任务接收到验证请求时


在 API 调用上,后两者都调用了 - [AFSecurityPolicy evaluateServerTrust:forDomain:] 方法来判断当前服务器是否被信任,我们会在接下来的文章中具体介绍这个方法的实现的作用。


Objective-C


- (void)URLSession:(NSURLSession *)session              task:(NSURLSessionTask *)taskdidReceiveChallenge:(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 = NSURLSessionAuthChallengeRejectProtectionSpace; } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } }
if (completionHandler) { completionHandler(disposition, credential); }}
复制代码


如果没有传入 taskDidReceiveAuthenticationChallenge block,只有在上述方法返回 YES 时,才会获得认证凭证 credential

引入 AFNetworkReachabilityManager 监控网络状态

AFSecurityPolicy 相同,AFURLSessionManager 对网络状态的监控是由 AFNetworkReachabilityManager 来负责的,它仅仅是持有一个 AFNetworkReachabilityManager 的对象。


真正需要判断网络状态时,仍然需要开发者调用对应的 API 获取网络状态

小结

  1. AFURLSessionManager 是对 NSURLSession 的封装

  2. 它通过 - [AFURLSessionManager dataTaskWithRequest:completionHandler:] 等接口创建 NSURLSessionDataTask 的实例

  3. 持有一个字典 mutableTaskDelegatesKeyedByTaskIdentifier 管理这些 data task 实例

  4. 引入 AFURLSessionManagerTaskDelegate 来对传入的 uploadProgressBlock downloadProgressBlock completionHandler 在合适的时间进行调用

  5. 实现了全部的代理方法来提供 block 接口

  6. 通过方法调剂在 data task 状态改变时,发出通知


关于其他 AFNetworking 源代码分析的其他文章:



本文转载自 Draveness 技术博客。


原文链接:https://draveness.me/afnetworking2


2019-12-10 17:51843

评论

发布
暂无评论
发现更多内容

PancakeSwap市值管理机器人APP系统开发价格

惊艳!阿里自爆用480页讲清楚了44种微服务架构设计模式

Java~~~

Java spring 架构 面试 微服务

手撕HashMap源码

程序员阿杜

Java 源码

🚄【Redis 干货领域】让你彻底会使用“Redis中最陌生且最强大的集合”(ZSET)【上部】

洛神灬殇

redis Zset 9月日更 Redis指令

顶级!13位专家力荐Spring5为企业级开发提供一站式方案

Java~~~

Java spring 架构 面试 Spring Boot

GameFi/DeFi+NFT软件系统开发方案

完美!华为爆出Redis宝典,原来Redis性能可压榨到极致

Java~~~

Java redis 架构 面试 分布式

回款金额自动分配

明道云

iOS 屏幕实时共享功能实践(内附详细代码)

融云 RongCloud

ios 音视频

牛掰!阿里人用7部分讲明白百亿级高并发系统(全彩版小册开源)

Java~~~

Java 架构 面试 多线程 高并发

Swap市值管理机器人系统软件开发资料

云小课|VMware备份上云学习专列来了,快加入吧~

华为云开发者联盟

云备份 VMware备份 备份上云

mac idea配置类和方法的注释

孙强

方法 Mac IDEA 添加注释

限量!腾讯高工用4部分讲清楚了Spring全家桶+微服务

Java~~~

Java spring 架构 面试 微服务

测试用例编写方法

与风逐梦

RVB2601应用开发实战系列五: 网络播放器设计(一)

Roy夹馍

物联网 risc-v 嵌入式开发

面面俱到!阿里巴巴2021最新Java面试参考权威指南泰山版震撼来袭

Java 架构 面试 后端 计算机

GameFi游戏金融系统软件开发介绍

TLS协议分析 (一) 设计目标及历史

OpenIM

uniswap市值管理机器人系统开发

进大厂为何要学Zookeeper?

冰河

zookeeper 分布式 一致性 服务注册与发现 协同系统

快速解决运维过程中碰到的难题,就用行云管家!

行云管家

运维 运维人生 IT运维 企业运维

game+defi系统软件开发内容

RVB2601应用开发实战系列四:FOTA镜像升级

Roy夹馍

物联网 risc-v 嵌入式开发

细节爆炸!腾讯用13个案例实战讲明白MySQL,没想到这么全

Java~~~

Java MySQL 数据库 架构 面试

限时!字节Java程序性能优化宝典开源,原来这才叫性能优化

Java~~~

Java 架构 面试 JVM 性能调优

云上数据不安全主要原因是什么?保障云上数据安全用什么软件好?

行云管家

云计算 数据安全 企业上云 云数据

RVB2601应用开发实战系列三: GUI图形显示

Roy夹馍

物联网 risc-v 嵌入式开发

九阴真经MySQL版:一条查询语句如何执行的

MySQL 数据库 程序员 架构

美团架构师熬夜整理:Netty权威指南2.0版+英雄传说项目

Java~~~

Java 架构 面试 Netty 架构师

高光时刻!美团推出Spring源码进阶宝典:脑图+视频+文档

Java~~~

Java spring 源码 架构 面试

AFNetworking 的核心 AFURLSessionManager(二)_语言 & 开发_Draveness_InfoQ精选文章