GitHub Repo: Coderzsq.project. ios Follow: coderZsq · GitHub Blog: Castie! Note the Resume: coderzsq. Making. IO/coderzsq. Pr…

Everyday bullshit

Emm ~~~ some time ago and went out to look at the iOS market, unless it is BAT, TMD, such a well-known enterprise, the other as long as you work hard or can pass, here to share some tips, such as when HR, or others push you, no matter how the requirements of writing, Do not be intimidated by these things, you can first reverse the project, understand the engineering architecture and code specifications, as well as the use of third-party libraries, in fact, is a good preliminary understanding of the other party before the interview, know yourself, of course, to increase the chances of winning. Of course, the current requirements for iOS are also uneven, and the interviewer’s ability and vision will also cause some problems for you when you enter the job. For example, once, the interviewer asked, can you tell me about Swift in detail? What Angle do you want me to use to answer such a broad proposition… Compiler level? Write time copy at the language level? Or grammatical? A new feature of WWDC? Pick one side, and that’s all they say? Can we not just use keywords? And then the look of contempt that accompanies it… The key is a girl interviewer, and when I reverse the project found that the quality of the code is actually average, forget it, so as not to be said that the impact is not good, but the interview is blind BB ~~

Of course, the requirements for iOS have been greatly improved than before, and it is almost difficult to find a job now that I can only write a Tableview in OC. However, I will not touch OC code except for the company’s projects, so when I want to write the PERFORMANCE optimization of OC version, I do not remember a lot of syntax and need to check it now. I can’t remember the API at all.

When doing the performance optimization, of course, you still need to know some C language knowledge of the underlying, although these may not be able to be reflected on the code to be able to, but to understand and design is a very big help, and now on the Internet about the underlying principle of blog so much, are you copy my I copy you, there may be a little moisture, I recommend run-time 723, GNUStep, such source code to look at their own will be more reliable, of course, look at these code may not understand the C++ syntax, can go to my github page to see some of the C++ syntax summary, should be quite helpful.

The preparatory work

For the performance optimization of the interface, the simple is to keep the interface smooth not to drop frames, of course, the principle of this kind of online search a lot, if you have time to look at YYKit will be able to know about. In principle, when the Vsync signal arrives within 16.67ms of the CPU having typesetted, drawn, decoded, and GPU avoiding off-screen rendering, it will render to the screen the next time Runloop comes.

Not so much theory, theory is never as meaningful as practice, and this is the so-called Bayesian algorithm is always modified strategy. Let’s first write a test data for later optimization needs.

const http = require('http');

http.createServer((req, res) = > {

    if (decodeURI(req.url) == "/fetchMockData") {
        console.log(req.url);

        let randomNumber = function (n) {
            var randomNumber = "";
            for (var i = 0; i < n; i++)
                randomNumber += Math.floor(Math.random() * 10);
            return randomNumber;
        }

        let randomString = function(len) {&emsp; &emsp; len = len ||32; &emsp; &emsp;var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; &emsp; &emsp;varmaxPos = $chars.length; &emsp; &emsp;var randomString = ' '; &emsp; &emsp;for (i = 0; i < len; i++) {&emsp; &emsp; &emsp; &emsp; randomString += $chars.charAt(Math.floor(Math.random() * maxPos)); &emsp; &emsp; }&emsp; &emsp;return randomString;
        }

        let json = [];
        for (let i = 0; i < 100; i++) {
            let obj = {};
            let texts = [];
            let images = [];
            for (let i = 0; i < randomNumber(4); i++) {
                texts.push(randomString(randomNumber(1)));
            }
            for (let i = 0; i < 16; i++) {
                images.push("https://avatars3.githubusercontent.com/u/19483268?s=40&v=4");
            }
            obj.texts = texts;
            obj.images = images;
            json.push(obj);
        }
        res.writeHead(200, {
            'Content-Type': 'application/json'
        });

        res.end(JSON.stringify({
            data: json,
            status: 'success'
        }));
    } 

}).listen(8080);
console.log('listen port = 8080');
Copy the code

If you don’t have a Node environment on your machine, you are advised to install it yourself.

After completion, we can access the test data through the browser. The test data is divided into two parts, one is the text, the other is the URL of the picture. Because of the trouble, I directly used my Github profile picture.

Preliminary layout

For smooth interface, the first thing that comes to mind is pre-typesetting, which plays a significant role and has a very simple principle, namely asynchronous pre-calculation, avoiding repeated calculation every time in layoutSubviews and cell reuse. Aye… It’s about keywords again… But it’s that simple. What else is there to tell? Say again fine code is written out…

#import "Service.h"
#import <AFNetworking.h>

@implementation Service

- (void)fetchMockDataWithParam:(NSDictionary *)parameter completion:(RequestCompletionBlock)completion {
    
    AFHTTPSessionManager * manager = [AFHTTPSessionManager manager];
    [manager GET:@"http://localhost:8080/fetchMockData" parameters:parameter progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        if ([responseObject[@"status"] isEqualToString: @"success"]) {
            completion(responseObject[@"data"].nil);
        }
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        completion(nil, error);
    }];
}

@end
Copy the code

Of course, you need to request the simulated data, but notice that localhost is HTTP and you need to enforce it.

#import "ViewModel.h"
#import "ComponentModel.h"
#import "ComponentLayout.h"
#import "Element.h"
#import <UIKit/UIKit.h>

@implementation ViewModel

- (Service *)service {
    
    if(! _service) { _service = [Service new]; }return _service;
}

- (void)reloadData:(LayoutCompeltionBlock)completion error:(void(^) (void))errorCompletion {
    
    [self.service fetchMockDataWithParam:nil completion:^(NSArray<ComponentModel *> *models, NSError *error) {
        if (models.count > 0 && error == nil) {
            dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
            dispatch_async(queue, ^{
                NSMutableArray * array = [NSMutableArray new];
                for (ComponentModel * model in models) {
                    ComponentLayout * layout = [self preLayoutFrom:model];
                    [array addObject:layout];
                }
                dispatch_async(dispatch_get_main_queue(), ^{
                    if(completion) { completion(array); }}); }); }else{ errorCompletion(); }}]; } - (void)loadMoreData:(LayoutCompeltionBlock)completion {
    [self reloadData:completion error:nil];
}

- (ComponentLayout *)preLayoutFrom:(ComponentModel *)model {
    
    ComponentLayout * layout = [ComponentLayout new];
    layout.cellWidth = [UIScreen mainScreen].bounds.size.width;
    
    CGFloat cursor = 0;
    
    CGFloat x = 5.;
    CGFloat y = 0.;
    CGFloat height = 0.;
    
    NSMutableArray * textElements = [NSMutableArray array];
    NSArray * texts = model[@"texts"];
    for (NSUInteger i = 0; i < texts.count; i++) {
        NSString * text = texts[i];
        CGSize size = [text boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading  attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:7]} context:nil].size;
        if ((x + size.width) > layout.cellWidth) {
            x = 5;
            y += (size.height + 10);
            height = y + size.height + 5;
        }
        CGRect frame = CGRectMake(x, y, size.width + 5, size.height + 5);
        x += (size.width + 10);

        Element * element = [Element new];
        element.value = text;
        element.frame = frame;
        [textElements addObject:element];
    }
    cursor += height + 5;
    
    x = 5.; y = cursor; height = 0.;
    
    NSMutableArray * imageElements = [NSMutableArray array];
    NSArray * images = model[@"images"];
    for (NSUInteger i = 0; i < images.count; i++) {
        NSString * url = images[i];
        CGSize size = CGSizeMake(40.40);
        if ((x + size.width) > layout.cellWidth) {
            x = 5;
            y += (size.height + 5);
            height = (y - cursor) + size.height + 5;
        }
        CGRect frame = CGRectMake(x, y, size.width, size.height);
        x += (size.width + 5);
        
        Element * element = [Element new];
        element.value = url;
        element.frame = frame;
        [imageElements addObject:element];
    }
    cursor += height + 5;
    
    layout.cellHeight = cursor;
    layout.textElements = textElements;
    layout.imageElements = imageElements;
    return layout;
}

@end
Copy the code
- (void)setupData:(ComponentLayout *)layout asynchronously:(BOOL)asynchronously {
    _layout = layout; _asynchronously = asynchronously;
    
    [self displayImageView];
    if(! asynchronously) {for (Element * element in layout.textElements) {
            UILabel * label = (UILabel *)[_labelReusePool dequeueReusableObject];
            if(! label) { label = [UILabel new];
                [_labelReusePool addUsingObject:label];
            }
            label.text = element.value;
            label.frame = element.frame;
            label.font = [UIFont systemFontOfSize:7];
            [self.contentView addSubview:label]; } [_labelReusePool reset]; }}Copy the code

The code is very simple, that is, it is formatted according to the data obtained, and then the cell directly obtains the typeset frame and performs a single assignment. You can also check out Sunnyxx’s FDTemplateLayoutCell.

Reuse pool

If you have a sharp eye, you will see that the above code is made up of a reuse mechanism similar to the cell. This is actually the concept of the reuse pool described below. The principle is very simple, which is to maintain two queues, one is the current queue, the other is reusable queue.

#import "ReusePool.h"

@interface ReusePool(a)
@property (nonatomic.strong) NSMutableSet * waitUsedQueue;
@property (nonatomic.strong) NSMutableSet * usingQueue;
@property (nonatomic.strong) NSLock * lock;
@end

@implementation ReusePool

- (instancetype)init
{
    self = [super init];
    if (self) {
        _waitUsedQueue = [NSMutableSet set];
        _usingQueue = [NSMutableSet set];
        _lock = [NSLock new];
    }
    return self;
}

- (NSObject *)dequeueReusableObject {
    
    NSObject * object = [_waitUsedQueue anyObject];
    if (object == nil) {
        return nil;
    } else {
        [_lock lock];
        [_waitUsedQueue removeObject:object];
        [_usingQueue addObject:object];
        [_lock unlock];
        returnobject; }} - (void)addUsingObject:(UIView *)object {
    if (object == nil) {
        return;
    }
    [_usingQueue addObject:object];
}

- (void)reset {
    NSObject * object = nil;
    while((object = [_usingQueue anyObject])) { [_lock lock]; [_usingQueue removeObject:object]; [_waitUsedQueue addObject:object]; [_lock unlock]; }}@end
Copy the code

Because I use this reuse queue, it is not just for THE UI, so in the case of multi-threaded access, it is necessary to lock, so I use the most common pThread-mutex to lock.

Preliminary decoding

Here is done through the URL of asynchronous decoding processing, related to the principle of the article is actually a lot of their own.

@implementation NSString (Extension)

- (void)preDecodeThroughQueue:(dispatch_queue_t)queue completion:(void(^) (UIImage *))completion {
    
    dispatch_async(queue, ^{
        CGImageRef cgImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self]]].CGImage;
        CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage) & kCGBitmapAlphaInfoMask;
        
        BOOL hasAlpha = NO;
        if (alphaInfo == kCGImageAlphaPremultipliedLast ||
            alphaInfo == kCGImageAlphaPremultipliedFirst ||
            alphaInfo == kCGImageAlphaLast ||
            alphaInfo == kCGImageAlphaFirst) {
            hasAlpha = YES;
        }
        
        CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
        bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
        
        size_t width = CGImageGetWidth(cgImage);
        size_t height = CGImageGetHeight(cgImage);
        
        CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8.0.CGColorSpaceCreateDeviceRGB(), bitmapInfo);
        CGContextDrawImage(context, CGRectMake(0.0, width, height), cgImage);
        cgImage = CGBitmapContextCreateImage(context);
        
        UIImage * image = [[UIImage imageWithCGImage:cgImage] cornerRadius:width * 0.5];
        CGContextRelease(context);
        CGImageRelease(cgImage);
        completion(image);
    });
}

@end
Copy the code

pre-rendered

Such as imageView. Layer. The cornerRadius and imageView. Layer. MasksToBounds = YES; This type of operation will cause off-screen rendering and the GPU will consume the newly opened buffer. To avoid off-screen rendering, you should avoid using layer border, corner, shadow, mask, etc., and try to pre-draw the corresponding content in the background thread. This cache strategy of SDWebImage is of great reference significance.

@implementation UIImage (Extension)

- (UIImage *)cornerRadius:(CGFloat)cornerRadius {
    
    CGRect rect = CGRectMake(0.0.self.size.width, self.size.height);
    UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:cornerRadius];
    UIGraphicsBeginImageContextWithOptions(self.size, false[UIScreen mainScreen].scale);
    CGContextAddPath(UIGraphicsGetCurrentContext(), bezierPath.CGPath);
    CGContextClip(UIGraphicsGetCurrentContext());
    
    [self drawInRect:rect];
    
    CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke);
    UIImage * image = UIGraphicsGetImageFromCurrentImageContext(a);UIGraphicsEndImageContext(a);return image;
}

@end
Copy the code
- (void)displayImageView {
    for (Element * element in _layout.imageElements) {
        UIImageView * imageView = (UIImageView *)[_imageReusePool dequeueReusableObject];
        if(! imageView) { imageView = [UIImageView new];
            [_imageReusePool addUsingObject:imageView];
        }
        UIImage * image = (UIImage *)[ComponentCell.asyncReusePool dequeueReusableObject];
        if(! image) {NSString * url = element.value;
            [url preDecodeThroughQueue:concurrentQueue completion:^(UIImage * image) {
                [ComponentCell.asyncReusePool addUsingObject:image];
                dispatch_async(dispatch_get_main_queue(), ^{
                    imageView.image = image;
                });
            }];
        } else {
            imageView.image = image;
        }
        imageView.frame = element.frame;
        [self.contentView addSubview:imageView];
    }
    [ComponentCell.asyncReusePool reset];
    [_imageReusePool reset];
}
Copy the code

And this works really well in combination with reuse pooling, of course, because I have the same image here, normally you would have to design a whole cache strategy, hash urls and so on.

By combining the above schemes, we have been able to display that much data smoothly and the frame count is still good, but there is still room for optimization.

Asynchronous rendering

After the above optimization, we are actually doing pretty well, but look at the lower layer, is not a bit of trypophobia… Asynchronous drawing is a good solution to this problem. And optimize the fluency more. The principle of asynchronous drawing is asynchronous drawInLayer, asynchronous drawing in context and unified drawing.

- (void)displayLayer:(CALayer *)layer {
    
    if(! layer)return;
    if(layer ! =self.layer) return;
    if(! [layer isKindOfClass:[AsyncDrawLayerclass]]) return;
    if (!self.isAsynchronously) return;
    
    AsyncDrawLayer *tempLayer = (AsyncDrawLayer *)layer;
    [tempLayer increaseCount];
    
    NSUInteger oldCount = tempLayer.drawsCount;
    CGRect bounds = self.bounds;
    UIColor * backgroundColor = self.backgroundColor;
    
    layer.contents = nil;
    dispatch_async(serialQueue, ^{
        void (^failedBlock)(void) = ^ {NSLog(@"displayLayer failed");
        };
        if(tempLayer.drawsCount ! = oldCount) { failedBlock();return;
        }
        
        CGSize contextSize = layer.bounds.size;
        BOOL contextSizeValid = contextSize.width >= 1 && contextSize.height >= 1;
        CGContextRef context = NULL;
        
        if (contextSizeValid) {
            UIGraphicsBeginImageContextWithOptions(contextSize, layer.isOpaque, layer.contentsScale);
            context = UIGraphicsGetCurrentContext(a);CGContextSaveGState(context);
            if (bounds.origin.x || bounds.origin.y) {
                CGContextTranslateCTM(context, bounds.origin.x, -bounds.origin.y);
            }
            if(backgroundColor && backgroundColor ! = [UIColor clearColor]) {
                CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
                CGContextFillRect(context, bounds);
            }
            [self asyncDraw:YES context:context completion:^(BOOL drawingFinished) {
                if (drawingFinished && oldCount == tempLayer.drawsCount) {
                    CGImageRef CGImage = context ? CGBitmapContextCreateImage(context) : NULL;
                    {
                        UIImage * image = CGImage ? [UIImage imageWithCGImage:CGImage] : nil;
                        dispatch_async(dispatch_get_main_queue(), ^{
                            if(oldCount ! = tempLayer.drawsCount) { failedBlock();return;
                            }
                            layer.contents = (id)image.CGImage;
                            layer.opacity = 0.0;
                            [UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
                                layer.opacity = 1.0;
                            } completion:NULL];
                        });
                    }
                    if (CGImage) {
                        CGImageRelease(CGImage); }}else{ failedBlock(); }}];CGContextRestoreGState(context);
        }
        UIGraphicsEndImageContext(a); }); }Copy the code

There’s really nothing to talk about in the code except to pay attention to the problem of multithreaded redrawing.

- (void)asyncDraw:(BOOL)asynchronously context:(CGContextRef)context completion:(void(^) (BOOL))completion {
    
    for (Element * element in _layout.textElements) {
        NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
        paragraphStyle.lineBreakMode = NSLineBreakByCharWrapping;
        paragraphStyle.alignment = NSTextAlignmentCenter;
        [element.value drawInRect:element.frame withAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:7].NSForegroundColorAttributeName: [UIColor blackColor],
                                                                 NSParagraphStyleAttributeName:paragraphStyle}];
    }
    completion(YES);
}
Copy the code

layer

This way our layer will be composited and the frame rate will always be 60 frames.

Grammar skills

OC now supports class attributes as well. See renderTree in swift for details.

@property (class.nonatomic.strong) ReusePool * asyncReusePool;

static ReusePool * _asyncReusePool = nil;

+ (ReusePool *)asyncReusePool {
    if (_asyncReusePool == nil) {
        _asyncReusePool = [[ReusePool alloc] init];
    }
    return _asyncReusePool;
}

+ (void)setAsyncReusePool:(ReusePool *)asyncReusePool {
    if (_asyncReusePool != asyncReusePool) {
        _asyncReusePool = asyncReusePool;
    }
}
Copy the code

Run into the pit of

In fact, the ideal effect is something like the picture below. All images are drawn asynchronously to layer.

- (void)asyncDraw:(BOOL)asynchronously context:(CGContextRef)context completion:(void(^) (BOOL))completion {
    
    for (Element * element in _layout.textElements) {
        NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
        paragraphStyle.lineBreakMode = NSLineBreakByCharWrapping;
        paragraphStyle.alignment = NSTextAlignmentCenter;
        [element.value drawInRect:element.frame withAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:7].NSForegroundColorAttributeName: [UIColor blackColor],
                                                                 NSParagraphStyleAttributeName:paragraphStyle}];
    }
    completion(YES);
    //TODO:
    // for (Element * element in _layout.imageElements) {
    // UIImage * image = (UIImage *)[ComponentCell.asyncReusePool dequeueReusableObject];
    // if (! image) {
    // NSString * url = element.value;
    // [url preDecodeThroughQueue:concurrentQueue completion:^(UIImage * image) {
    // [ComponentCell.asyncReusePool addUsingObject:image];
    // [image drawInRect:element.frame]; // The asynchronous callback context cannot be retrieved
    // completion(YES);
    / /}];
    // } else {
    // [image drawInRect:element.frame];
    // completion(YES);
    / /}
    / /}
    // [_asyncReusePool reset];
}
Copy the code

But had a problem here, CGContextRef, namely UIGraphicsGetCurrentContext () to get the current thread’s context, and thread and Runloop is one-to-one, only the current thread context can draw, And network pictures need to be downloaded and decoded asynchronously, which is bound to be downloaded concurrently, so that the context can not be obtained and cannot be drawn. Error as follows:

I tried passing the drawing context to another thread and rendering:

    for (Element * element in _layout.imageElements) {
        UIImage * image = (UIImage *)[ComponentCell.asyncReusePool dequeueReusableObject];
        if(! image) {NSString * url = element.value;
            [url preDecodeThroughQueue:concurrentQueue completion:^(UIImage * image) {
                [ComponentCell.asyncReusePool addUsingObject:image];
                CGContextDrawImage(context, element.frame, image.CGImage);
                completion(YES);
            }];
        } else {
            [image drawInRect:element.frame];
            completion(YES);
        }
    }
    [_asyncReusePool reset];
Copy the code

Unfortunately, passing the current context with CGContextDrawImage does not solve this problem either, as the previous thread’s context is out of date…

Permanent thread

The first thing to think about is that the context may have failed after the last thread hung, so we can use runloop to open a resident thread to preserve the context.

#import "PermenantThread.h"

@interface Thread : NSThread
@end

@implementation Thread
- (void)dealloc {
    NSLog(@"%s", __func__);
}
@end

@interface PermenantThread(a)
@property (strong.nonatomic) Thread *innerThread;
@end

@implementation PermenantThread

- (instancetype)init
{
    if (self = [super init]) {
        self.innerThread = [[Thread alloc] initWithBlock:^{
            CFRunLoopSourceContext context = {0};
            CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
            CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
            CFRelease(source);
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0 e10.false);
        }];
        [self.innerThread start];
    }
    return self;
}


- (void)executeTask:(PermenantThreadTask)task {
    if (!self.innerThread || ! task)return;
    [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}

- (void)stop {
    if (!self.innerThread) return;
    [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}

- (void)dealloc {
    NSLog(@"%s", __func__);
    [self stop];
}

- (void)__stop {
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.innerThread = nil;
}

- (void)__executeTask:(PermenantThreadTask)task {
    task();
}

@end

Copy the code

However, the context expiration I thought was due to child thread destruction did not work after I made a resident thread with Runloop… Really drunk… Does anyone know what context expiration is?

The solution

I checked some information and asked some big guy, and got all kinds of feedback solutions. Dispath_async_f this function is useless, and the context passed is not related to the drawing context, so this is wrong. Finally, I tried to use self-built context to solve this problem.

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(a);CGContextRef context = CGBitmapContextCreate(NULL, contextSize.width, contextSize.height, 8, contextSize.width * 4, colorSpace, kCGImageAlphaPremultipliedFirst | kCGImageByteOrderDefault);
CGColorSpaceRelease(colorSpace);
CGAffineTransform normalState = CGContextGetCTM(context);
CGContextTranslateCTM(context, 0, bounds.size.height);
CGContextScaleCTM(context, 1.- 1);
CGContextConcatCTM(context, normalState);
if(backgroundColor && backgroundColor ! = [UIColor clearColor]) {
    CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
    CGContextFillRect(context, bounds);
}
UIGraphicsPushContext(context);
Copy the code

Instead of using drawInRect when drawing, use the CGContextDrawImage API

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
    for (Element * element in _layout.imageElements) {
        UIImage * image = (UIImage *)[ComponentCell.asyncReusePool dequeueReusableObject];
        if(! image) { dispatch_group_enter(group); dispatch_group_async(group, concurrentQueue, ^{NSString * url = element.value;
                [url preDecodeWithCGCoordinateSystem:YES completion:^(UIImage * image) {
                    [ComponentCell.asyncReusePool addUsingObject:image];
                    CGContextDrawImage(context, element.frame, image.CGImage);
// completion(YES);
                }];
                dispatch_group_leave(group);
            });
        } else {
            CGContextDrawImage(context, element.frame, image.CGImage);
            completion(YES);
        }
    }
    dispatch_group_notify(group, concurrentQueue, ^{
        completion(YES);
    });
    [_asyncReusePool reset];
Copy the code

Because it is asynchronous drawing, every picture downloaded for drawing, will cause constant brush frame, so use thread group for unified drawing.

The drawInRect and CGContextDrawImage are drawn in different coordinate systems.

Finally, all the source code for this article can be found on Github -> SQPerformance

Making Repo: coderZsq. Project. Ios Follow: coderZsq, making Resume: coderZsq. Making. IO/coderZsq. Pr…