1. UIView的绘制流程图
-
UIView
调用setNeedsDisplay
,但是没立即进行视图的绘制工作; -
UIView
调用setNeedDisplay
后,系统调用view
对应layer
的setNeedsDisplay
方法; - 当前
runloop
即将结束的时候调用CALayer
的display
方法; -
runloop
即将结束, 开始视图的绘制流程;
知识点:为什么调用
UIView
的setNeedsDisplay
后界面并没有立即绘制?
在当前Runloop
将要结束的时候才会开始界面的绘制;
2. 系统绘制流程图
1.
CALayer
内部创建一个backing store(CGContextRef
)();
- 判断layer是否有代理;
2.1 有代理:调用delegete
的drawLayer:inContext
, 然后在合适的实际回调代理, 在[UIView drawRect]
中做一些绘制工作;
2.2 没有代理:调用layer
的drawInContext
方法, -
layer
上传backingStore
到GPU
, 结束系统的绘制流程;
3. 异步绘制
什么是异步绘制?
个人理解为:展示界面的过程中将创建上下文和控件的绘制工作放到子线程中, 子线程将那些工作完成渲染成图片后转回主线程然后将位图展示在界面上;
异步绘制的入口在[layer.delegate displayLayer]
, 异步绘制过程中代理负责生成对应的位图(bitmap
);然后将bitmap
赋值给layer.content
属性展示;
关于异步绘制,可以参考下图片的的解码逻辑
例如imgV.image = [UIImage imageNamed:@"1.png"];
赋值左侧后其实并不是直接展示到屏幕上的,而是需要压缩图片的二进制数据, 然后再解码成屏幕可以识别的数据格式(位图); 这个解码过程默认都是在主线程进行的; 将其放在子线程则可以减轻主线程压力; 这个过程可以参考SDWebImage
中的+ (UIImage *)decodedImageWithImage:(UIImage *)image
函数的处理逻辑, 每次图片下载后是先在子线程将图片解码成位图, 然后用的时候在主线程展示;
异步绘制流程
- 某个时机调用
setNeedsDisplay
; -
runloop
将要结束的时候调用[CALayer display]
- 如果代理实现了
dispalyLayer
将会调用此方法, 在子线程中去做异步绘制的工作; - 子线程中做的工作:创建上下文, 控件的绘制, 生成图片;
- 转到主线程, 设置
layer.contents
, 将生成的视图展示在layer
上面;
异步绘制示例代码:
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AsyncLabel : UIView
@property (nonatomic, copy) NSString *asynText;
@property (nonatomic, strong) UIFont *asynFont;
@property (nonatomic, strong) UIColor *asynBGColor;
@end
NS_ASSUME_NONNULL_END
#import "AsyncLabel.h"
#import <CoreText/CoreText.h>
@implementation AsyncLabel
- (void)displayLayer:(CALayer *)layer {
/**
除了在drawRect方法中, 其他地方获取context需要自己创建[https://www.jianshu.com/p/86f025f06d62]
coreText用法简介:[https://www.cnblogs.com/purple-sweet-pottoes/p/5109413.html]
*/
CGSize size = self.bounds.size;;
CGFloat scale = [UIScreen mainScreen].scale;
///异步绘制:切换至子线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
UIGraphicsBeginImageContextWithOptions(size, NO, scale);
///获取当前上下文
CGContextRef context = UIGraphicsGetCurrentContext();
///将坐标系反转
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
///文本沿着Y轴移动
CGContextTranslateCTM(context, 0, size.height);
///文本反转成context坐标系
CGContextScaleCTM(context, 1.0, -1.0);
///创建绘制区域
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));
///创建需要绘制的文字
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:self.asynText];
[attStr addAttribute:NSFontAttributeName value:self.asynFont range:NSMakeRange(0, self.asynText.length)];
[attStr addAttribute:NSBackgroundColorAttributeName value:self.asynBGColor range:NSMakeRange(0, self.asynText.length)];
///根据attStr生成CTFramesetterRef
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attStr);
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attStr.length), path, NULL);
///将frame的内容绘制到content中
CTFrameDraw(frame, context);
UIImage *getImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
///子线程完成工作, 切换到主线程展示
dispatch_async(dispatch_get_main_queue(), ^{
self.layer.contents = (__bridge id)getImg.CGImage;
});
});
}
@end
#import "ViewController.h"
#import "AsyncLabel.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
AsyncLabel *asLabel = [[AsyncLabel alloc] initWithFrame:CGRectMake(50, 100, 200, 200)];
asLabel.backgroundColor = [UIColor cyanColor];
asLabel.asynBGColor = [UIColor greenColor];
asLabel.asynFont = [UIFont systemFontOfSize:16 weight:20];
asLabel.asynText = @"学习异步绘制相关知识点, 学习异步绘制相关知识点";
[self.view addSubview:asLabel];
///不调用的话不会触发 displayLayer方法
[asLabel.layer setNeedsDisplay];
}
@end
示例代码下载
日常开发中推荐YYKit作者出的YYAsyncLayer
参考文档:
iOS UIGraphicsGetCurrentContext()的使用
iOS CoreText原理及基本使用方法
iOS 图片的解码