ios UI绘制原理

在 UIView 中都有一个 CALayer 的属性,负责 UIView 具体内容的显示。具体过程是系统会把 UIView 显示的内容(包括 UILabel 的文字,UIImageView 的图片等)绘制在一张画布上,完成后倒出成位图赋值给 CALayer 的 contents 属性,完成显示。

这其中的工作都是在主线程中完成的,这就导致了主线程频繁的处理 UI 绘制的工作,如果要绘制的元素过多,过于频繁,就会造成卡顿。

那么是否可以将复杂的绘制过程放到后台线程中执行,从而减轻主线程负担,来提升 UI 流畅度呢?

答案是可以的,系统给我们留下的异步绘制的口子,请看下面的流程图,它是我们进行基本绘制的基础。

一、UIView的绘制流程

下面我们来对这个流程进行说明:

  • 当我们调用[UIView setNeedsDisplay]方法时,并没有执行立即执行绘制工作。
  • 而是马上调用[view.layer setNeedsDisplay]方法,给当前layer打上脏标记, 表明需要绘制了。
  • 在当前RunLoop快要结束的时候调用layer 的display方法,来进入到当前视图的真正绘制当中。
  • 在layer的display方法内部,系统会判断layer的layer.delegate(默认是对应的UIView, layer上的子layer默认delegate为nil)是否实现了displayLayer:方法,
    a.如果没有实现,则执行系统的绘制流程;
    b.如果实现了则会进入异步绘制的入口
  • 最后把绘制完的backing store(可以理解为位图)提交给GPU,也就是将生成的 backing store 赋值给 layer.content 属性。。

二、系统绘制流程

对流程加以说明:

  1. 在layer内部会创建一个backing store,我们可以理解为CGContextRef上下文。
  2. 判断layer是否有delegate:
    2.1 如果有delegate,则会执行[layer.delegate drawLayer:inContext](这个方法的执行是在系统内部执行的),然后在这个方法中会调用view的drawRect:方法,也就是我们重写view的drawRect:方法才会被调用到。
    2.2 如果没有delegate,会调用layer的drawInContext方法,也就是我们可以重写的layer的该方法,此刻会被调用到。
  3. 最后都由CALayer把绘制完的backing store(可以理解为位图)提交给GPU。

三、异步绘制

//如果layer的delegate能respondsTo下面这个方法会调用异步绘制
- (void)displayLayer:(CALayer *)layer;

代理负责生成对应的bitmap,设置该bitmap作为layer.contents属性的值

当view调用setNeedsDisplay的时候,CALayer会调用display,如果代理实现了displayLayer:方法的话,就可以在子线程中去进行位图的绘制,完成后返回主线程把位图赋值给layer的contents属性,完成异步绘制。

下面是一个小demo, 在子线程绘制了一句话, 然后生成位图, 放到这个LoginView.layer.contents上, 

#import "LoginView.h"
@import CoreText.CTFramesetter;

@implementation LoginView

- (void)displayLayer:(CALayer *)layer {

    CGSize size = self.bounds.size;
    // 此处还是主线程
    NSLog(@"%s,%@",__func__,[NSThread currentThread]);

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
        CGContextRef context = UIGraphicsGetCurrentContext();

        // 将坐标系上下翻转,因为底层坐标系和 UIKit 坐标系原点位置不同。
        CGContextSetTextMatrix(context, CGAffineTransformIdentity);
        CGContextTranslateCTM(context, 0, size.height); // 原点为左下角
        CGContextScaleCTM(context, 1, -1);

        CGMutablePathRef path = CGPathCreateMutable();
        CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));


        NSString * str = @"白日依山尽,黄河入海流";
        NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc]initWithString:str];
        [attrStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:14] range:NSMakeRange(0, str.length)];

        CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrStr);
        CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrStr.length), path, NULL);
        CTFrameDraw(frame, context);

        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        // 回到主线程设置layer.contents
        dispatch_async(dispatch_get_main_queue(), ^{
            layer.contents = (__bridge id _Nullable)(image.CGImage);

//            layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"Image"].CGImage);
        });
    });

}

@end

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值