YYKit-YYAsyncLayer分析
1. YYAsyncLayer是什么?
YYAsyncLayer继承于CALayer的异步图层。
2. YYAsyncLayer的作用?
对于一些需要更好性能的试图,可以使用YYAsyncLayer实现异步绘制,优化试图性能。
3. YYAsyncLayer如何实现?
1. YYTransaction
YYTransaction是用于在主线程的runloop中添加observer,用于在runloop休眠之前执行相关方法。
我们查看下YYTransaction的源码:
//1. 创建一个YYTransaction的对象
+ (YYTransaction *)transactionWithTarget:(id)target selector:(SEL)selector{if (!target || !selector) return nil;YYTransaction *t = [YYTransaction new];t.target = target;t.selector = selector;return t;
}//2. 提交-往主线程的runloop中添加观察者
- (void)commit {if (!_target || !_selector) return;YYTransactionSetup();[transactionSet addObject:self];
}//由于使用NSMutableSet存储自定义对象,而NSMutableSet使用HashMap实现存储结构的。hashmap中需要先寻找hash索引,以便快速的找到对应存储的对象。
- (NSUInteger)hash {long v1 = (long)((void *)_selector);long v2 = (long)_target;return v1 ^ v2;
}//在hashmap中寻找对象的时候,当找到hash索引后,如果对应位置没有值,则直接存储,否则需要判断对象是否相等,如果相等则放弃存储,否则指定位置使用链表或者再次寻址。
- (BOOL)isEqual:(id)object {if (self == object) return YES;if (![object isMemberOfClass:self.class]) return NO;YYTransaction *other = object;return other.selector == _selector && other.target == _target;
}
其中我们可以看下YYTransactionSetup的实现:
static NSMutableSet *transactionSet = nil;static void YYTransactionSetup() {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{//创建一个NSMutableSet对象,用于存储监听指定Activity的Transaction对象,以备回调顺序处理transactionSet = [NSMutableSet new];//获取主线程的runloopCFRunLoopRef runloop = CFRunLoopGetMain();CFRunLoopObserverRef observer;//创建观察者,注意CFIndex,这个参数指定相同activity对应的多个Transaction回调函数执行的优先级,0表示优先级最高,越往后优先级越低observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(),kCFRunLoopBeforeWaiting | kCFRunLoopExit,true, // repeat0xFFFFFF, // after CATransaction(2000000)--界面更新YYRunLoopObserverCallBack, NULL);CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);CFRelease(observer);});
}//回调函数分析
static void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {if (transactionSet.count == 0) return;NSSet *currentSet = transactionSet;transactionSet = [NSMutableSet new];[currentSet enumerateObjectsUsingBlock:^(YYTransaction *transaction, BOOL *stop) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"[transaction.target performSelector:transaction.selector];
#pragma clang diagnostic pop}];
}
2. YYSentinel
暂时没有理解为什么要使用YYSentinel
3. YYAsyncLayer
YYAsyncLayer是CALayer的子类,用于异步渲染内容。
YYAsyncLayerDelegate是YYAsyncLayer的代理,存在如下代理方法:
- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask;
返回一个YYAsyncLayerDisplayTask对象
@interface YYAsyncLayerDisplayTask : NSObject//异步绘制之前调用
@property (nullable, nonatomic, copy) void (^willDisplay)(CALayer *layer);//绘制内容的时候调用
@property (nullable, nonatomic, copy) void (^display)(CGContextRef context, CGSize size, BOOL(^isCancelled)(void));//在绘制完成后调用
@property (nullable, nonatomic, copy) void (^didDisplay)(CALayer *layer, BOOL finished);@end
以下我们仔细分析一下YYAsyncLayer源码:
//初始化一个YYAsyncLayer对象
- (instancetype)init {self = [super init];static CGFloat scale; //globalstatic dispatch_once_t onceToken;dispatch_once(&onceToken, ^{scale = [UIScreen mainScreen].scale;});self.contentsScale = scale;_sentinel = [YYSentinel new];_displaysAsynchronously = YES;return self;
}- (void)dealloc {[_sentinel increase];
}//重写setNeedsDisplay方法
- (void)setNeedsDisplay {[self _cancelAsyncDisplay];[super setNeedsDisplay];
}//重写display方法
- (void)display {//主要作用是什么?super.contents = super.contents;[self _displayAsync:_displaysAsynchronously];
}- (void)_displayAsync:(BOOL)async {__strong id<YYAsyncLayerDelegate> delegate = (id)self.delegate;YYAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];if (!task.display) {if (task.willDisplay) task.willDisplay(self);self.contents = nil;if (task.didDisplay) task.didDisplay(self, YES);return;}//异步绘制if (async) {if (task.willDisplay) task.willDisplay(self);YYSentinel *sentinel = _sentinel;int32_t value = sentinel.value;BOOL (^isCancelled)(void) = ^BOOL() {return value != sentinel.value;};CGSize size = self.bounds.size;BOOL opaque = self.opaque;CGFloat scale = self.contentsScale;CGColorRef backgroundColor = (opaque && self.backgroundColor) ? CGColorRetain(self.backgroundColor) : NULL;if (size.width < 1 || size.height < 1) {CGImageRef image = (__bridge_retained CGImageRef)(self.contents);self.contents = nil;if (image) {dispatch_async(YYAsyncLayerGetReleaseQueue(), ^{CFRelease(image);});}if (task.didDisplay) task.didDisplay(self, YES);CGColorRelease(backgroundColor);return;}dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{if (isCancelled()) {CGColorRelease(backgroundColor);return;}UIGraphicsBeginImageContextWithOptions(size, opaque, scale);CGContextRef context = UIGraphicsGetCurrentContext();if (opaque) {CGContextSaveGState(context); {if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) {CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));CGContextFillPath(context);}if (backgroundColor) {CGContextSetFillColorWithColor(context, backgroundColor);CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));CGContextFillPath(context);}} CGContextRestoreGState(context);CGColorRelease(backgroundColor);}task.display(context, size, isCancelled);if (isCancelled()) {UIGraphicsEndImageContext();dispatch_async(dispatch_get_main_queue(), ^{if (task.didDisplay) task.didDisplay(self, NO);});return;}UIImage *image = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();if (isCancelled()) {dispatch_async(dispatch_get_main_queue(), ^{if (task.didDisplay) task.didDisplay(self, NO);});return;}dispatch_async(dispatch_get_main_queue(), ^{if (isCancelled()) {if (task.didDisplay) task.didDisplay(self, NO);} else {self.contents = (__bridge id)(image.CGImage);if (task.didDisplay) task.didDisplay(self, YES);}});});} else { //同步绘制[_sentinel increase];if (task.willDisplay) task.willDisplay(self);UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, self.contentsScale);CGContextRef context = UIGraphicsGetCurrentContext();if (self.opaque) {CGSize size = self.bounds.size;size.width *= self.contentsScale;size.height *= self.contentsScale;CGContextSaveGState(context); {if (!self.backgroundColor || CGColorGetAlpha(self.backgroundColor) < 1) {CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));CGContextFillPath(context);}if (self.backgroundColor) {CGContextSetFillColorWithColor(context, self.backgroundColor);CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));CGContextFillPath(context);}} CGContextRestoreGState(context);}task.display(context, self.bounds.size, ^{return NO;});UIImage *image = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();//设置显示的contents内容self.contents = (__bridge id)(image.CGImage);if (task.didDisplay) task.didDisplay(self, YES);}
}//取消异步显示
- (void)_cancelAsyncDisplay {[_sentinel increase];
}我们看下全局渲染队列://根据当前系统激活的处理器的数量与最大限制进程数一起设置队列最大的数,防止死锁或者线程之间切换的低效率
static dispatch_queue_t YYAsyncLayerGetDisplayQueue() {
#ifdef YYDispatchQueuePool_hreturn YYDispatchQueueGetForQOS(NSQualityOfServiceUserInitiated);
#else
#define MAX_QUEUE_COUNT 16 //设置最大的队列数为16static int queueCount;static dispatch_queue_t queues[MAX_QUEUE_COUNT]; //队列数组static dispatch_once_t onceToken;static int32_t counter = 0;dispatch_once(&onceToken, ^{//获取当前进程的系统处于激活状态的处理器数量queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;//根据处理器的数量和设置的最大队列数来设定当前队列数组的大小queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {for (NSUInteger i = 0; i < queueCount; i++) {//创建同步队列dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);queues[i] = dispatch_queue_create("com.ibireme.yykit.render", attr);}} else {for (NSUInteger i = 0; i < queueCount; i++) {queues[i] = dispatch_queue_create("com.ibireme.yykit.render", DISPATCH_QUEUE_SERIAL);dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));}}});int32_t cur = OSAtomicIncrement32(&counter);if (cur < 0) cur = -cur;//返回对应的一个同步队列return queues[(cur) % queueCount];
#undef MAX_QUEUE_COUNT
#endif
}static dispatch_queue_t YYAsyncLayerGetReleaseQueue() {
#ifdef YYDispatchQueuePool_hreturn YYDispatchQueueGetForQOS(NSQualityOfServiceDefault);
#elsereturn dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
#endif
}
4. YYAsyncLayer为什么这样设计?
1. YYAsyncLayer是参照了UIView的试图显示么?
- 我们先看一下通常的UIView试图,一旦创建UIView,一个CALayer自然被创建。而UIView是CALayer的delegate。当CALayer要显示的时候,CALayer会委托UIView提供要显示的内容,比如UIView中的drawRect等方法。而此时的UIView的内容都是在主线程中创建的。
- 对照YYAsyncLayer,我们在创建UIView的子类的时候,实现了:
+(CALayer *)layer
{return [YYAsyncLayer class];
}
这使得YYAsyncLayer成为了UIView的子类的layer层,而对应UIView的子类成为了YYAsyncLayer的delegate。当runloop中注册的试图渲染回调过来的时候,会触发display方法,而在重载的display中,我们调用了_displayAsync进行异步/同步绘制的函数。在这个函数中我们回调delegate中的相关绘制,最终将bitmap设置为layer的contents属性中用于内容显示操作。
2. 为什么要创建队列数组来实现异步操作,而不是在单一的一个队列或者在一个全局队列中实现异步操作?
在单核的手机系统中,通过时间片轮询的方式实现了异步的操作。但是对于时间片轮询,每个任务设置的时间片如果太大,同时任务比较多,这个时候位于末尾的任务可能等待的时间会比较久。如果每个任务设置的时间片过小,对于一个任务时间片用完,就得切换到新的任务并且给其设置时间片,这个切换过程是比较耗费cpu资源的。
当前较新的iphone手机都具备多核(iphone7:4核),这让多线程实现更加有效率。但是如果过多的分配多线程,在单核手机系统中出现的时间片轮询中环境切换耗费cpu资源问题同样存在。由于以上问题,我们也得控制并发线程数。
YYAsyncLayer中通过以下代码实现了控制并发线程数的目的:
static dispatch_queue_t YYAsyncLayerGetDisplayQueue() {//对于实现了YYDispatchQueuePool,直接使用YYDispatchQueueGetForQOS获取
#ifdef YYDispatchQueuePool_hreturn YYDispatchQueueGetForQOS(NSQualityOfServiceUserInitiated);
#else
#define MAX_QUEUE_COUNT 16 //设置最大的并发线程数static int queueCount;//同步队列数组static dispatch_queue_t queues[MAX_QUEUE_COUNT];static dispatch_once_t onceToken;static int32_t counter = 0;dispatch_once(&onceToken, ^{//获取根据当前进程的系统激活的核数queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;//根据核数和设置的最大并发数确定队列的个数queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {for (NSUInteger i = 0; i < queueCount; i++) {dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);queues[i] = dispatch_queue_create("com.ibireme.yykit.render", attr);}} else {for (NSUInteger i = 0; i < queueCount; i++) {queues[i] = dispatch_queue_create("com.ibireme.yykit.render", DISPATCH_QUEUE_SERIAL);dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));}}});int32_t cur = OSAtomicIncrement32(&counter);if (cur < 0) cur = -cur;//轮询返回对应的同步队列return queues[(cur) % queueCount];
#undef MAX_QUEUE_COUNT
#endif
}以上代码通过设置最大的同步队列数组长度来控制最大并发数。在使用队列进行异步操作中:
dispatch_async(queue,^{异步操作
});
其中queue是通过以上代码中同步队列数组轮询返回一个同步队列,然后异步执行同步队列,其中同步队列中的任务是同步执行的。除了通过异步同步队列之外,我们也可以通过锁的方式(信号量)实现控制并发数self.semaphore = dispatch_semaphore_create(3);__weak typeof(self) weakSelf = self;for (int i = 0 ; i < 1000; i ++ ) {dispatch_semaphore_wait(weakSelf.semaphore, 3000);dispatch_async(dispatch_get_global_queue(0, 0), ^{dispatch_semaphore_wait(weakSelf.semaphore, 3000);});}
3. 使用atomic_fetch_add_explicit(原子操作)有什么特殊含义?
暂时未理解在项目中使用原子操作的原因
4. YYTransaction为什么要在CATransaction提交渲染后在执行setNeedsDisplay方法呢?
CALayer的display方法调用是在UIView显示调用了setNeedDisplay或者setNeedDisplayInRect之后调用的。而YYAsyncLayer的异步绘制也在display中执行。
YYKit-YYAsyncLayer分析相关推荐
- YYAsyncLayer 源码剖析:异步绘制
引言 性能优化一直是 iOS 开发中的一个重头戏,其中界面流畅度的优化是至关重要的,因为它直接关系到用户体验.从最熟悉和简单的 UIKit 框架到 CoreAnimation.CoreGraphics ...
- App性能分析数据监控
App性能分析数据监控 APP的性能监控包括: CPU 占用率.内存使用情况.网络状况监控.启动时闪退.卡顿.FPS.使用时崩溃.耗电量监控.流量监控等等. 文中所有代码都已同步到github中,有兴 ...
- YYAsyncLayer 学习
简介 YYAsyncLayer是用于图层异步绘制的一个组件,将耗时操作(如文本布局计算)放在RunLoop空闲时去做,进而减少卡顿. 组件内容 YYAsyncLayer主要有3个类. 1, YYTra ...
- 专访 YYKit 作者 ibireme: 开源大牛是怎样炼成的
前言 第一次听到 ibireme 这个名字,是看到他在 微博上分享 了 YYText 开源库.当时我第一眼见到 YYText 的功能示意 GIF 图时(下图所示),就被它丰富的功能吸引了.YYText ...
- YYImage实现思路源码分析(图片解压缩原理)
YYKit组件之一---->YYImage 图像处理 移动端图片格式调研 图片处理的小技巧 YYWebImage源码分析 YYModel源码分析 YYText源码分析 核心思路--->图片 ...
- 一组功能丰富的iOS组件:YYKit
YYKit 是一组庞大.功能丰富的 iOS 组件. 为了尽量复用代码,这个项目中的某些组件之间有比较强的依赖关系.为了方便其他开发者使用,我从中拆分出以下独立组件: -YYModel - 高性能的 i ...
- 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析
目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...
- 2022-2028年中国自动驾驶系统行业现状调研分析报告
[报告类型]产业研究 [报告价格]4500起 [出版时间]即时更新(交付时间约3个工作日) [发布机构]智研瞻产业研究院 [报告格式]PDF版 本报告介绍了中国自动驾驶系统行业市场行业相关概述.中国自 ...
- 2022-2028年中国阻尼涂料市场研究及前瞻分析报告
[报告类型]产业研究 [报告价格]4500起 [出版时间]即时更新(交付时间约3个工作日) [发布机构]智研瞻产业研究院 [报告格式]PDF版 本报告介绍了中国阻尼涂料行业市场行业相关概述.中国阻尼涂 ...
最新文章
- P5357 【模板】AC自动机(二次加强版)(AC自动机建fail树dfs求模式串出现次数)
- 【RHCE学习笔记】基于安全的NFS认证(kerberos)
- MNE-Python : TypeError: today() takes no keyword arguments
- JS中URL编码参数(UrlEncode)
- 网络营销推广软件教你学会单页面SEO优化技巧,轻松赢流量!
- Qt5标准文件对话框类
- Java - Collection
- Python程序退出方式小结(亲测)
- WNetAddConnection2 映射网络驱动器
- linux创建用户,并修改分组,改变权限
- 离散哈特莱变换(DHT)及快速哈特莱变换(FHT)学习
- Yapi Mock 远程代码执行漏洞
- NET sturct值类型
- 计算机等级考试绝对应用,96年4月至210年全国计算机等级考试绝对全收集.docx
- 手机linux比windows省电,为什么win10比ubuntu省电?
- go语言和php哪个建站好,从0开始Go语言,用Golang搭建网站
- Java的“ for each”循环如何工作?
- 优先队列的优先级设置法
- 【Python实例第25讲】稳健的 vs 经验的协方差估计
- 盗梦空间http://acm.nyist.net/JudgeOnline/problem.php?pid=125
热门文章
- JDBC驱动加载机制详解以及spi机制
- Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request p
- 7步完成使用LitePal数据库
- 利用Tushare获取金融数据
- mysql 配置文件详解
- android 导出Excel列表
- android马赛克,iOS Android 去马赛克处理
- 【说透中台】05 | D4模型:中台规划建设方法论概述
- 卖桃子问题(递归函数求解)
- Meteorographica:一个用Python绘制天气图的气象代码库