YYAsyncLayer的示例
示例中
[[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];- (void)contentsNeedUpdated { // do update [self.layer setNeedsDisplay];}- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask { //..}复制代码
1 YYTransaction
看看 YYTransaction , 根据名字 这应该是 处理事物相关的 类。
不得不说 这个注释真好/** YYTransaction let you perform a selector once before current runloop sleep. */@interface YYTransaction : NSObject复制代码
可以看出 YYTransaction 是 用来将 selector 在 runloop sleep 前 提交到 runloop 中 处理的。
YYTransaction 存储了target
和 selector
用来在runloop observer callback 中执行对应方法 1.1 Commit
注意commit 中的注释,如果 相同的 transaction 已经提交到 runloop 中了,这个方法什么都不会做.
/** Commit the trancaction to main runloop. @discussion It will perform the selector on the target once before main runloop's current loop sleep. **If the same transaction (same target and same selector) has already commit to runloop in this loop, this method do nothing.** */- (void)commit;复制代码
这是怎么实现的呢?
- (void)commit { if (!_target || !_selector) return; // 在Commit 中 做 单例的初始化 很好 隐藏了很多细节,使用着通过 简单的调用即可 添加 transcation YYTransactionSetup(); [transactionSet addObject:self];}复制代码
可以注意到transactionSet 既然是Set 那么 是不会存在两个相同的元素,系统会自动删掉一个元素
在 Objective-C
中 通过 isEqual:
方法 来测试和其他对象的想等性
isEqual:
和hash
来支持根据 _selector
,_target
判断想等性. - (NSUInteger)hash { long v1 = (long)((void *)_selector); long v2 = (long)_target; return v1 ^ v2;}- (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;}复制代码
1.2 Observe RunLoop
YYTransaction 通过观察Runloop的waiting或Exit状态 ,通过回调,执行 transactionSet 中的 transaction
// 注册 Runloop Observerstatic void YYTransactionSetup() { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ transactionSet = [NSMutableSet new]; CFRunLoopRef runloop = CFRunLoopGetMain(); CFRunLoopObserverRef observer; observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopBeforeWaiting | kCFRunLoopExit, true, // repeat 0xFFFFFF, // after CATransaction(2000000) YYRunLoopObserverCallBack, NULL); CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes); CFRelease(observer); });}// Runloop Observer Callbackstatic void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { if (transactionSet.count == 0) return; NSSet *currentSet = transactionSet; // 更新 trasactionSet 保证 callback 执行后,对象不会被持有 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 YYAsyncLayer
/** The YYAsyncLayer class is a subclass of CALayer used for render contents asynchronously. @discussion When the layer need update it's contents, it will ask the delegate for a async display task to render the contents in a background queue. */@interface YYAsyncLayer : CALayer复制代码
可以看到 YYAsyncLayerDelegate 的 newAsyncDisplayTask 是提供了 YYAsyncLayer 需要在后台队列绘制的内容.
2.1 YYAsyncLayerDisplayTask
YYAsyncLayerDisplayTask 有如下的属性
@property (nullable, nonatomic, copy) void (^willDisplay)(CALayer *layer);- display@property (nullable, nonatomic, copy) void (^display)(CGContextRef context, CGSize size, BOOL(^isCancelled)(void));@property (nullable, nonatomic, copy) void (^didDisplay)(CALayer *layer, BOOL finished);复制代码
display 在mainthread或者background thread调用 这要求 display 应该是线程安全的
willdisplay 和 didDisplay 在 mainthread 调用。newAsyncDisplayTask 是提供了 YYAsyncLayer 需要在后台队列绘制的内容.
2.1 YYAsyncLayer 异步绘制
通过 重写display 方法,异步绘制 self.contents
- (void)display { super.contents = super.contents; [self _displayAsync:_displaysAsynchronously];}复制代码
- (void)_displayAsync:(BOOL)async
中在后台队列执行 task.display 的 block 进行绘制任务,最后在主线程中 将绘制结果的图片赋值给 contents.
self.contents = (__bridge id)(image.CGImage);复制代码
2.2 取消绘制任务
当 TableView 快速滑动时,会有大量异步绘制任务提交到后台线程去执行。但是有时滑动速度过快时,绘制任务还没有完成就可能已经被取消了。如果这时仍然继续绘制,就会造成大量的 CPU 资源浪费,甚至阻塞线程并造成后续的绘制任务迟迟无法完成。
- (void)setNeedsDisplay { [self _cancelAsyncDisplay]; [super setNeedsDisplay];}- (void)_cancelAsyncDisplay { [_sentinel increase];}复制代码
这个跟 tableview 的 cell 重用有关
由于 cell 重用,那么 当重用的cell绘制新的内容时,就会调用setNeedDisplay 方法.这是可以再次取消上一次的后台绘制任务,在进行新的绘制.利用 YYSentinel 来完成任务的取消
/** YYSentinel is a thread safe incrementing counter. It may be used in some multi-threaded situation. */@interface YYSentinel : NSObject复制代码
value 用来保存任务刚开始时 sentinel.value
int32_t value = sentinel.value;复制代码
如果任务执行过程中 发现snetinel.value 和 保存的value,则就是认为任务以及取消了
BOOL (^isCancelled)() = ^BOOL() { return value != sentinel.value;};复制代码
3 如何使用
- YYAsyncLayerDelegate 的 - (YYAsyncLayerDisplayTask *)newAsyncDisplayTask 提供了绘制所需的task
- 在设置可以涉及到 视图内容改变的 操作时,[[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];
- contentsNeedUpdated 的操作就是 [self.layer setNeedsDisplay]; 更新视图
菜?一个,望大佬多指教