YYAsyncLayer 源码解析

YYAsyncLayer的示例YYAsyncLayerios

示例中 git

[[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];

- (void)contentsNeedUpdated {
    // do update
    [self.layer setNeedsDisplay];
}
- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask {
    //..
}复制代码

1 YYTransaction

看看 YYTransaction , 根据名字 这应该是 处理事物相关的 类。
不得不说 这个注释真好github

/**
 YYTransaction let you perform a selector once before current runloop sleep.
 */
@interface YYTransaction : NSObject复制代码

能够看出 YYTransaction 是 用来将 selector 在 runloop sleep 前 提交到 runloop 中 处理的。
YYTransaction 存储了 targetselector 用来在runloop observer callback 中执行对应方法安全

1.1 Commit

注意commit 中的注释,若是 相同的 transaction 已经提交到 runloop 中了,这个方法什么都不会作.bash

/**
 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 那么 是不会存在两个相同的元素,系统会自动删掉一个元素async

Objective-C 中 经过 isEqual: 方法 来测试和其余对象的想等性
经过重写isEqual:hash 来支持根据 _selector,_target 判断想等性.oop

- (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 Observer
static 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 Callback
static 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 须要在后台队列绘制的内容. ui

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 资源浪费,甚至阻塞线程并形成后续的绘制任务迟迟没法完成。iOS 保持界面流畅的技巧

- (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 如何使用

  1. YYAsyncLayerDelegate 的 - (YYAsyncLayerDisplayTask *)newAsyncDisplayTask 提供了绘制所需的task
  2. 在设置能够涉及到 视图内容改变的 操做时,[[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];
  3. contentsNeedUpdated 的操做就是 [self.layer setNeedsDisplay]; 更新视图

一些其余的阅读在这个仓库里
github.com/JunyiXie/Op…

菜🐔一个,望大佬多指教

相关文章
相关标签/搜索