Example of YYAsyncLayer YYAsyncLayer
In the sample
[[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];
- (void)contentsNeedUpdated {
// do update
[self.layer setNeedsDisplay];
}
- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask {
/ /..
}Copy the code
1 YYTransaction
Take a look at YYTransaction, which by its name should be the class that deals with things. I have to say that’s a good note
/**
YYTransaction let you perform a selector once before current runloop sleep.
*/
@interface YYTransaction : NSObjectCopy the code
You can see that YYTransaction is used to commit the selector to the runloop for processing before the runloop sleep. YYTransaction stores the target and selector used to execute corresponding methods in the Runloop Observer callback
1.1 the Commit
Note the comments in commit that this method does nothing if the same transaction has already been committed to the 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;Copy the code
How does this work?
- (void)commit {
if(! _target || ! _selector)return;
// Initialization of singletons in Commit hides many details, using simple calls to add transcation
YYTransactionSetup();
[transactionSet addObject:self];
}Copy the code
Note that transactionSet is a Set, so no two elements are the same. The system will delete one element automatically
In objective-c we test equality with other objects using isEqual: and we override isEqual: and hash to support equality by _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;
}Copy the code
1.2 Observe RunLoop
YYTransaction executes transaction in transactionSet by observing a Runloop waiting or Exit state and by calling back
// Register Runloop Observer
static void YYTransactionSetup() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
transactionSet = [NSMutableSet new];
CFRunLoopRef runloop = CFRunLoopGetMain(a);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;
// Update trasactionSet to ensure that the object is not held after callback execution
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
}];
}Copy the code
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 : CALayerCopy the code
You can see that YYAsyncLayerDelegate’s newAsyncDisplayTask provides what YYAsyncLayer needs to draw in the background queue.
2.1 YYAsyncLayerDisplayTask
YYAsyncLayerDisplayTask has the following properties
@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);Copy the code
Display is called on the mainthread or the background thread and that requires display to be thread-safe willdisplay and didDisplay to be called on the mainthread.
NewAsyncDisplayTask provides what YYAsyncLayer needs to draw in the background queue.
2.1 YYAsyncLayer Asynchronous drawing
Draw self.contents asynchronously by overriding the display method
- (void)display {
super.contents = super.contents;
[self _displayAsync:_displaysAsynchronously];
}Copy the code
– (void)_displayAsync:(BOOL)async executes the task.display block in the background queue to perform the drawing task, and finally assigns the drawing image to contents in the main thread.
self.contents = (__bridge id)(image.CGImage);Copy the code
2.2 Canceling a Drawing Task
When the TableView slides quickly, a large number of asynchronous drawing tasks are submitted to the background thread for execution. But sometimes when the slide is too fast, the drawing task can be canceled before it is finished. If you continue drawing at this point, you will waste a lot of CPU resources, and even block the thread and delay the completion of subsequent drawing tasks. IOS tips for keeping the interface smooth
- (void)setNeedsDisplay {
[self _cancelAsyncDisplay];
[super setNeedsDisplay];
}
- (void)_cancelAsyncDisplay {
[_sentinel increase];
}Copy the code
And this has to do with cell reuse in tableView because of cell reuse, when the reused cell draws something new, it calls the setNeedDisplay method. It is possible to cancel the previous background drawing task again and make a new drawing.
Use YYSentinel to complete mission cancellations
/**
YYSentinel is a thread safe incrementing counter.
It may be used in some multi-threaded situation.
*/
@interface YYSentinel : NSObjectCopy the code
Value is used to save sentinel.value at the beginning of the task
int32_t value = sentinel.value;Copy the code
If snetinel.value and saved value are found during task execution, the task is considered and cancelled
BOOL (^isCancelled)() = ^BOOL() {
returnvalue ! = sentinel.value; };Copy the code
3 How to Use it
- YYAsyncLayerDelegate’s – (YYAsyncLayerDisplayTask *)newAsyncDisplayTask provides the task needed to draw
- In the set can involve to view the content change of operation, [[YYTransaction transactionWithTarget: self selector: @ the selector (contentsNeedUpdated)] commit];
- The action contentsNeedUpdated is [self.layer setNeedsDisplay]; Update the view
Some other reading in this warehouse github.com/JunyiXie/Op…
Dish 🐔 1, hope big guy more advice