一,AFNetworking
这是 AFNetworking 发起一个 Get 请求的流程图,大概可以分为这几个步骤。
1,创建 NSMutableURLRequest对象,也就是设置request的请求头和请求体
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
2,创建NSURLSessionDataTask对象
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
}
} else {
if (success) {
NSLog(@" 6当前线程 %@",[NSThread currentThread]);
success(dataTask, responseObject);
}
}
}];
3,在创建NSURLSessionDataTask对象时是同步创建,防止创建对应的混乱,我们创建了一个任务task1 对应completionHandler1,然后又创建了task2 对应的completionHandler2,这时候在task2数据还没有返回的前提下,task1的数据返回了,就会调用completionHandler2,就是这样的一个bug,造成任务的创建是不安全的,不过这个问题已经在ios8后修复了。
__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
static dispatch_queue_t url_session_manager_creation_queue() {
static dispatch_queue_t af_url_session_manager_creation_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
});
return af_url_session_manager_creation_queue;
}
static void url_session_manager_create_task_safely(dispatch_block_t block) {
if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
// Fix of bug
// Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
// Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
dispatch_sync(url_session_manager_creation_queue(), block);
} else {
block();
}
}
创建AFURLSessionManagerTaskDelegate代理对象,用它接管了Foundation框架中NSURLSession/NSURLSessionTask/NSURLSessionDataTask/NSURLSessionDownloadTask等类中的NSURLSessionDelegate/NSURLSessionTaskDelegate/NSURLSessionDataDelegate/NSURLSessionDownloadDelegate等代理的回调
- (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;
}
通过 NSURLSessionTask 的 taskIdentifier 标识符对 delegate 进行管理,只要是用于识别该NSURLSessionTask 的代理
- (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];
}
初始化NSURLSession对象时,设置了代理
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
KVO监听的属性值发生变化
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)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);
}
}
}
4,响应回调的处理
等待系统代理方法的回调
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
[delegate URLSession:session dataTask:dataTask didReceiveData:data];
if (self.dataTaskDidReceiveData) {
self.dataTaskDidReceiveData(session, dataTask, data);
}
}
创建一个并行队列用来管理数据的处理。目的是加快数据的处理速度。
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//Performance Improvement from #2672
NSData *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;
}
if (error) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
//个人感觉内部使用dispatch_group_async用来异步派发执行completionHandler回调是为了留给调用者使用dispatch_group_notify等函数有机会监听completionHandler执行完成后的回调
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];
});
});
} else {
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];
});
});
});
}
#pragma clang diagnostic pop
}
二,AsyncDisplayKit:让iOS应用界面极致流畅
第一点理解ui操作必须在主线程上的原理(其实也可以在子线程进行,只是风险大):因为uikit不是线程安全的。两个线程同时设置同一个UIView的背景颜色,那么很有可能渲染显示的是颜色A,而此时在UIView逻辑树上的背景颜色属性为B。
两个线程同时操作view的树形结构:在线程A中for循环遍历并操作当前View的所有subView,然后此时线程B中将某个subView直接删除,这就 导致了错乱还可能导致应用崩溃。
第二点:UIView与CALayer的关系:
每个 UIView 内部都有一个 CALayer 在背后提供内容的绘制和显示。
uiview可以响应事件,calayer不能
在 View显示的时候,UIView 做为 Layer 的 CALayerDelegate,View 的显示内容由内部的 CALayer 的 display。
UIView 和 CALayer 都不是线程安全的,并且只能在主线程创建、访问和销毁。
第三AsyncDisplayKit:
原理理解,系统的界面更新过程,当有界面更新操作,先提交到一个全局容器中。runloop跑到BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 时,由于苹果注册了一个 Observer 监听runloop状态,会回调去执行提交到全局容器待处理的ui操作,先执行实际的更新和调整,在提交到gpu渲染到屏幕上。
而AsyncDisplayKit的更新过程:
它也在runloop注册了observer,同样监听runloop的BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) ,然后回调去执行在子线程完成的事物并提交到gpu渲染到屏幕上。
需要注意的是如果有文本和图片,也会在绘制阶段做一些优化,
1,将当前视图的子视图压缩成一层绘制在当前页面上
2,使用 - displayWithParameters:isCancelled: 返回一个 UIImage,对图像节点 ASImageNode 进行绘制
3,使用 - drawRect:withParameters:isCancelled:isRasterizing: 在 CG 上下文中绘制文字节点 ASTextNode
这三种方式都通过 ASDK 来优化视图的渲染速度。
注意:比系统更新少了绘制阶段,绘制阶段放到了子线程,绘制阶段对文本和图片做了优化,渲染时要快。
这篇文章介绍的很好:https://zhuanlan.zhihu.com/p/22255533?refer=iOS-Source-Code。
三个ASDK主要优化的方面:
- 布局:
iOS自带的Autolayout在布局性能上存在瓶颈,并且只能在主线程进行计算。因此ASDK弃用了Autolayout。 - 渲染
对于大量文本,图片等的渲染,UIKit组件只能在主线程并且可能会造成GPU绘制的资源紧张。ASDK使用了一些方法,比如图层的预混合等,并且异步的在后台绘制图层,不阻塞主线程的运行。 - 系统对象创建于销毁
UIKit组件封装了CALayer图层的对象,在创建、调整、销毁的时候,都会在主线程消耗资源。ASDK自己设计了一套Node机制,也能够调用。
ASDK最大的特点就是"异步"。
将消耗时间的渲染、图片解码、布局以及其它 UI 操作等等全部移出主线程,这样主线程就可以对用户的操作及时做出反应,来达到流畅运行的目的。
三,FMDB
1,FMDatabaseQueue中创建了一个窜行队列,并使用dispatch_synco同步执行,来保证线程安全。
2,写入数据时需要保证只有一个FMDatabaseQueue实例对象
3,读取数据时可以使用FMDatabase来获取数据,不需要考虑线程安全.(文件数据库sqlite,同一时刻允许多个进程/线程读,但同一时刻只允许一个线程写).
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:@"/tmp/tmp.db"];
[queue inDatabase:^(FMDatabase *db) {
if ([db executeUpdate:@"INSERT INTO person VALUES (?, ?, ?)", @0, @"Demon", @20]) {
NSLog(@"Demon 插入成功 - %@", [NSThread currentThread]);
}
}];
四,MJExtension
设计一个NSObject的分类实现两个类方法
第一个类方法使用运行时获取属性的名称
第二个方法通过kvc设置属性的值
+ (NSArray *)yf_objcProperties
{
NSArray *ptyList = objc_getAssociatedObject(self, kPropertyListKey);
if (ptyList) {
return ptyList;
}
unsigned int outCount = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &outCount);
NSMutableArray *mtArray = [NSMutableArray array];
for (unsigned int i = 0; i < outCount; i++) {
objc_property_t property = propertyList[i];
const char *propertyName_C = property_getName(property);
NSString *propertyName_OC = [NSString stringWithCString:propertyName_C encoding:NSUTF8StringEncoding];
[mtArray addObject:propertyName_OC];
}
objc_setAssociatedObject(self, kPropertyListKey, mtArray.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
free(propertyList);
return mtArray.copy;
}
+ (instancetype)yf_objcWithDict:(NSDictionary *)dict
{
id objc = [[self alloc]init];
NSArray *propertyList = [self yf_objcProperties];
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
if ([propertyList containsObject:key]) {
[objc setValue:obj forKey:key];
}
}];
return objc;
}
五,uiview从绘制到渲染的过程 YYText
运行一段动画的过程可以分为6个阶段:
1> 布局(Layout)- 为视图/图层准备层级关系,以及设置图层属性(位置,背景色,边框等等)的阶段。调用layoutSubviews方法;
调用addSubview:方法;
2> 显示(Display) - 图层的寄宿图片被绘制的阶段。绘制涉及到-drawRect:和-drawLayer:inContext:方法的调用。通过drawRect绘制视图;
绘制string(字符串)
- (void)drawRect:(CGRect)rect{
NSLog(@"drawRect");
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(10, 10)];
[path addLineToPoint:CGPointMake(20, 20)];
[path closePath];
path.lineWidth = 1;
[[UIColor redColor] setStroke];
[path stroke];
}
3> 准备提交(Prepare) - Image decoding, Image conversion(如果图片类型不是GPU所支持的,需要对图片进行转换)。
4> 提交 - Core Animation打包所有的图层和动画,然后通过IPC(进程内通信)发送到渲染服务(render server,一个单独管理动画和图层组合的一个系统进程)。这个步骤是递归的,所以如果layer tree如果比较复杂此步骤代价比较高。
上面4个步骤发生在自己的应用程序内部,动画显示到屏幕之前还有2个步骤的工作:
5> 对所有图层属性计算中间值,设置OpenGL几何形状来执行渲染。
6> 在屏幕上渲染可见的三角形。
前5个阶段都在软件层面处理(通过CPU),只有最后一个阶段被GPU执行。6个阶段中只有布局和显示两个阶段是可以被我们控制的,Core Animation框架处理剩下的事务。