Momentous upheaval to

“Boom, boom, boom, boom, boom, boom, boom, boom.”

“Master, master, emergency rescue.”

“Don’t know shaoxia, hurry to let the old husband out of the customs for what?”

“The master taught me iOS performance optimization (primary) and iOS performance optimization (intermediate), I have been familiar with the study for many days, and study hard, so far has been able to solve most of the sliding lag problem.”

“Shaoxia, you are so smart.”

“Recently, however, still encountered problems, small pool do you want to be a page similar to the microblog home page, there are a lot of feed, each feed, and there are topics, links, images and expressions, rounded head, etc., so many elements of mixed together, even if I use lifetime studies, but still there will be caton, could not reach the requirements of small pool to fluency, so is very upset, I beg the master for advice.”

“So that’s how my husband is going to help you break through the bottleneck and make it to the next level.”

Asynchronous rendering

In both iOS Performance Tuning (elementary) and iOS Performance Tuning (intermediate), we did a lot of work for screen fluency and achieved good results. But no matter what you do, the final drawing is submitted to the system, which by default does all this on the main thread, and when you have to draw too many elements, too often, you still get stuck.

Can we put the drawing process in a background thread as we do with complex data?

Happily, the answer is yes.

So UIView in iOS has a CALayer *layer property in it, and the content of UIView, which is actually displayed by the layer, has a property in the layer called ID contents, and the contents are what you want to display, and in most cases, The value of contents is an image. All of the things that we use to display in UILabel or UIImageView are actually drawn on a canvas, and when you draw it, you export the image from the canvas, and then you assign the image to layer.contents.

Asynchronous painting is asynchronously drawing content on a canvas.

Its small

Talk is cheap. Show me the code

Start by creating a new AsyncLabel class and override the – (void)displayLayer:(CALayer *)layer method to draw asynchronously.

#import <UIKit/UIKit.h>NS_ASSUME_NONNULL_BEGIN @interface AsyncLabel: UIView @property(nonatomic, copy) NSString *text; // Set the font @property(nonatomic, strong) UIFont *font; @end NS_ASSUME_NONNULL_ENDCopy the code
#import "AsyncLabel.h"
#import <CoreText/CoreText.h>

@implementation AsyncLabel

- (void)displayLayer:(CALayer *)layer
{
    NSLog(@"Is it the main thread %d?", [[NSThread currentThread] isMainThread]); // Output 1 means the main thread is drawn asynchronously, so we are using the global subqueue. In practice, Dispatch_async (dispatch_get_global_queue(0, 0), ^{block CGSize size = CGSizeZero; __block CGFloat scale = 1.0; dispatch_sync(dispatch_get_main_queue(), ^{ size = self.bounds.size; scale = [UIScreen mainScreen].scale; }); UIGraphicsBeginImageContextWithOptions(size, NO, scale); CGContextRef context = UIGraphicsGetCurrentContext(); [self draw:context size:size]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); dispatch_async(dispatch_get_main_queue(), ^{ self.layer.contents = (__bridge id)(image.CGImage); }); }); } @endCopy the code
- (void)draw:(CGContextRef)context size:(CGSize)size {// flip the coordinate system up and down. Because the base frame and THE UIKit frame have different origin positions. CGContextSetTextMatrix(context, CGAffineTransformIdentity); CGContextTranslateCTM(context, 0, size.height); CGContextScaleCTM (context, 1.0, 1.0); CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height)); / / set the content NSMutableAttributedString * attString = [[NSMutableAttributedString alloc] initWithString: self. The text]; / / set the font [attString addAttribute: NSFontAttributeName value: the self. The font range: NSMakeRange (0, the self. The text. The length)]; CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString); CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attString.length), path, NULL); // Draw frame into context CTFrameDraw(frame, context); }Copy the code

This completes a simple drawing. In the – (void)displayLayer:(CALayer *)layer method, in the asynchronous thread, create a canvas and pass the drawing result to layer.contents in the main thread.

Drawing process using CoreText, here is only a simple text to draw up, the actual use of the process, according to the need may have a lot of places to set, please learn CoreText by yourself.

Call it to see the result:

AsyncLabel *label = [[AsyncLabel alloc] initWithFrame:CGRectMake(50, 200, [UIScreen mainScreen].bounds.size.width - 2 * 50, 100)];
label.backgroundColor = [UIColor lightGrayColor];
label.text = @"It's a good day to get everything you want, a good day to get peace, peace, peace.";
label.font = [UIFont systemFontOfSize:20];
[self.view addSubview:label];
[label.layer setNeedsDisplay];
Copy the code

Display effect achieved.

“Thanks for the master’s advice, master’s operation, let me understand.”

Find everything new and fresh

The above operation is a very general operation, and there are several problems that need to be solved in practical use:

  1. When asynclabels are used in a cell, there are a large number of asynclabels and they are constantly redrawn. The problem of child threads should be handled properly and they cannot be placed in the global queue (because there may be tasks submitted by the system in the global queue).
  2. The encapsulation of different types such as text and pictures.

Here’s the old man to introduce a new solution to shaoxia, refresh conventional ideas, and encapsulate excellence.

YYAsyncLayer

Its main processing flow is as follows:

  1. Register one in the main thread runLoopobserverIts priority is higher than that of the systemCATransactionMake sure the system does what it needs to do first.
  2. Group the operations that need to be drawn asynchronously. You can set the font, the color, the background, you don’t just draw one, you collect them all,runloopWill be inobserverThe timing of the need to notify unified processing.
  3. When it is time to process, an asynchronous draw is performed and the result of the draw is passed to the main threadlayer.contents.

With that in mind, let’s use YYAsyncLayer

Delete the code that was previously drawn asynchronously in asynclabel. m using the original method and add the following code

- (void)setText:(NSString *)text {
    _text = text.copy;
    [[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];
}

- (void)setFont:(UIFont *)font {
    _font = font;
    [[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];
}

- (void)layoutSubviews {
    [super layoutSubviews];
    [[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];
}

- (void)contentsNeedUpdated {
    // do update
    [self.layer setNeedsDisplay];
}
Copy the code

This code performs steps 1 and 2 in the process, registers an Observer, and collects actions to be processed uniformly.

+ (Class)layerClass
{
    return [YYAsyncLayer class];
}

- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask {
    
    YYAsyncLayerDisplayTask *task = [YYAsyncLayerDisplayTask new];
    task.willDisplay = ^(CALayer *layer) {
        //...
    };
    
    task.display = ^(CGContextRef context, CGSize size, BOOL(^isCancelled)(void)) {
        if (isCancelled()) {
            return;
        }
        if(! self.text.length) {return;
        }
        [self draw:context size:size];
    };
    
    task.didDisplay = ^(CALayer *layer, BOOL finished) {
        if (finished) {
            // finished
        } else {
            // cancelled
        }
    };
    
    return task;
}
Copy the code

These codes realize 3, asynchronous drawing in the process, and provide the user with willDisplay, display, didDisplay blocks.

Note that + (Class)layerClass must be overridden to enter the custom subLayer execution method. It’s the same thing as calling UIView layer, going from the default layer to subLayer.

The above moves, the old man is just a simple demonstration, but the little man encountered more complex than the old man. Xiaoxia is intelligent, must not be arrogant, but also need good practice and with runloop, CoreText use, can be accomplished. Go and answer the little sister.