技术点

Graver源码分析(1)——绘制

2021-05-31  本文已影响0人  无悔zero

之前了解了CALayerYYAsyncLayer,在这基础上来了解一下Graver。简单来说就是异步绘制图片代替复杂的界面。

Graver 是什么

Graver 是一款高效的 UI 渲染框架,它以更低的资源消耗来构建十分流畅的 UI 界面。Graver 独创性的采用了基于绘制的视觉元素分解方式来构建界面,得益于此,该框架能让 UI 渲染过程变得更加简单、灵活。

功能特点

  • 性能表现优异

Graver 在 FPS、CPU、Memory 各方面的指标均表现优异。

  • “一站式”异步化

Graver 从文本计算、样式排版渲染、图片解码,再到绘制,实现了全程异步化,并且是线程安全的。使用 Graver 可以一站式获得全部性能优化点。

  • 性能消耗的“边际成本”几乎为零

Graver 渲染整个过程除画板视图外完全没有使用 UIKit 控件,最终产出的结果是一张位图(Bitmap),视图层级、数量大幅降低。

  • 渲染速度快

Graver 并发进行多个画板视图的渲染、显示工作。得益于图文混排技术的应用,达到了内存占用低,渲染速度快的效果。由于排版数据是不变的,所以内部会进行缓存、复用,这又进一步促进了整体渲染效率。Graver 既做到了高效渲染,又保证了低时延页面加载。

  • 以“少”胜“繁”

Graver 重新抽象封装 CoreText、CoreGraphic 等系统基础能力,通过少量系统标准图形绘制接口即可实现复杂界面的展示。

  • 基于位图(bitmap)的轻量事件交互系统

如上述所说,界面展示从传统的视图树转变为一张位图,而位图不能响应、区分内部具体位置的点击事件。Graver 提供了基于位图的轻量事件交互系统,可以准确识别点击位置发生在位图的哪一块“绘制单元”内。该“绘制单元”可以理解为与我们一贯使用的某个具体UI控件相对应的视觉展示。使用 Graver 为某一视觉展示添加事件如同使用系统 UIButton 添加事件一样简单。

  • 全新的视觉元素分解思路

Graver 一改界面编程思路,与传统的通过控件“拼接”、“添加”,视图排列组合方式构建界面不同,它提供了十分灵活、便捷的接口让我们以“视觉所见”的方式构建界面。

  1. 我们直接从WMGMixedView入手,使用时继承它即可,它有一个继承链,可以理解为封装解耦:
  1. 在之前的CALayer绘制流程中了解到,通过设置layersetNeedsDisplay标记需要刷新,然后调用CALayerdisplay方法,再判断UIViewdisplayLayer方法是否实现,如果实现走自定义绘制流程,否则进入系统绘制流程,会先创建buffercontext,然后调用drawLayer:InContext:,再执行UIViewdrawRect
@implementation WMGAsyncDrawView
...
- (void)setNeedsDisplay
{
    [self.layer setNeedsDisplay];
}
...
- (void)displayLayer:(CALayer *)layer
{
    ...
    [self _displayLayer:(WMGAsyncDrawLayer *)layer rect:self.bounds drawingStarted:^(BOOL drawInBackground) {
        [self drawingWillStartAsynchronously:drawInBackground];
    } drawingFinished:^(BOOL drawInBackground) {
        [self drawingDidFinishAsynchronously:drawInBackground success:YES];
    } drawingInterrupted:^(BOOL drawInBackground) {
        [self drawingDidFinishAsynchronously:drawInBackground success:NO];
    }];
}

- (void)_displayLayer:(WMGAsyncDrawLayer *)layer
                 rect:(CGRect)rectToDraw
       drawingStarted:(WMGAsyncDrawCallback)startCallback
      drawingFinished:(WMGAsyncDrawCallback)finishCallback
   drawingInterrupted:(WMGAsyncDrawCallback)interruptCallback
{
    ...
    void (^drawBlock)(void) = ^{
        ...
        if (contextSizeValid) {
            UIGraphicsBeginImageContextWithOptions(contextSize, layer.isOpaque, layer.contentsScale);//开始绘制
            
            context = UIGraphicsGetCurrentContext();
            ...
            if (layer.drawingCount != targetDrawingCount)
            {
                drawingFinished = NO;
            }
            else
            {
                drawingFinished = [self drawInRect:rectToDraw withContext:context asynchronously:drawInBackground userInfo:drawingUserInfo];
            }
            
            CGContextRestoreGState(context);
        }
        UIGraphicsEndImageContext();//结束绘制
    };
    ...
}
@implementation WMGMixedView
...
- (BOOL)drawInRect:(CGRect)rect withContext:(CGContextRef)context asynchronously:(BOOL)asynchronously userInfo:(NSDictionary *)userInfo
{
    [super drawInRect:rect withContext:context asynchronously:asynchronously userInfo:userInfo];
    ...
    [_textDrawer drawInContext:context visibleRect:visibleRect replaceAttachments:_pendingAttachmentUpdates shouldInterruptBlock:^BOOL{
        ...
    }];
    
    return YES;
}
@implementation WMGTextDrawer
...
- (void)drawInContext:(CGContextRef)ctx visibleRect:(CGRect)visibleRect replaceAttachments:(BOOL)replaceAttachments shouldInterruptBlock:(WMGTextDrawerShouldInterruptBlock)block
{
//绘制流程有点复杂忽略掉,简单来说就是绘制一个面时先绘制每一行开始的每个单元开始绘制(点线面)
    ...
    if (replaceAttachments) {
        [self _drawAttachmentsInContext:ctx shouldInterrupt:block];//附件(图片)
    }
    ...
}

- (void)_drawAttachmentsInContext:(CGContextRef)ctx shouldInterrupt:(WMGTextDrawerShouldInterruptBlock)shouldInterrupt
{
    ...
    [self.textLayout.layoutFrame.arrayLines enumerateObjectsUsingBlock:^(WMGTextLayoutLine *line, NSUInteger idx, BOOL *stop) {
        [line enumerateRunsUsingBlock:^(NSUInteger idx, NSDictionary *attributes, NSRange characterRange, BOOL *stop) {
            ...
            if (self->_delegateHas.placeAttachment) {
                [self.delegate textDrawer:self replaceAttachment:attachment frame:frame context:ctx];//占位图
            } else if (attachment.type == WMGAttachmentTypeStaticImage) {
                
                if ([attachment.contents isKindOfClass:[NSString class]]) {
                    UIGraphicsPushContext(ctx);
                    UIImage *image = [UIImage imageNamed:(NSString *)attachment.contents];
                    [image drawInRect:frame];//图片
                    UIGraphicsPopContext();
                } else if ([attachment.contents isKindOfClass:[UIImage class]]) { ... }
            }
        }];
        interrupt_if_needed;
    }];
  1. 最后图片也绘制完成便结束了,如果图片需要网络加载,则在绘制完成后使用SDWebImage进行加载,也就是回到第2步回调中:
@implementation WMGMixedView
...
- (void)drawingDidFinishAsynchronously:(BOOL)asynchronously success:(BOOL)success
{
    ...
    // 三个点: 锁重入、for循环遍历移除元素、多线程同步访问共享数据区
    for (__block int i = 0; i < _arrayAttachments.count; i++) {
        if (i >= 0) {
            WMGTextAttachment *att = [_arrayAttachments objectAtIndex:i];
            
            if (att.type == WMGAttachmentTypeStaticImage){
                WMGImage *gImage = (WMGImage *)att.contents;
                if ([gImage isKindOfClass:[WMGImage class]]) {
                    
                    [gImage wmg_loadImageWithUrl:gImage.downloadUrl options:0 progress:NULL completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
                        ...
                    }];
                }
            }
        }
    }
    [_lock unlock];
}
@implementation WMGImage
...
- (void)wmg_loadImageWithUrl:(NSString *)urlStr options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDExternalCompletionBlock)completion
{
    NSURL *url = [NSURL URLWithString:urlStr];
    
    if (url) {
        __weak typeof(self) wself = self;
        //使用SDWebImage进行网络加载
        [[SDWebImageManager sharedManager] loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
            ...
        }];
    } else { ... }
}
绘制层级
上一篇 下一篇

猜你喜欢

热点阅读