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

  1. YYAsyncLayerDelegate’s – (YYAsyncLayerDisplayTask *)newAsyncDisplayTask provides the task needed to draw
  2. In the set can involve to view the content change of operation, [[YYTransaction transactionWithTarget: self selector: @ the selector (contentsNeedUpdated)] commit];
  3. 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