Graver初探
美团开源Graver框架:用“雕刻”诠释iOS端UI界面的高效渲染
Graver是一个App渲染框架,采用异步渲染的方式,很好的解决了App渲染时的性能损耗。今天我们带着两个问题:1,UIView的绘制过程。2,如何实现异步绘制成图片的。
UIView的绘制流程
UIView的绘制离不开CALayer,UIView的layer是CALayer,CALayer的delegate(CALayerDelegate)是UIView,CALayer主要负责内容,当内容改变时通过CALayerDelegate代理方法来询问UIView的渲染实现。
966599-a9a7090908e3588a.png
我们看一下UIView的drawRect的函数调用栈:
Snip20200207_7.png
首先会调用 display方法,该方法会默认询问CALayerDelegate的 - (void)displayLayer:(CALayer *)layer;方法,先后在调用 drawInContext:,该方法会默认询问 CALayerDelegate的- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;方法,最后调用drawRect:方法。
在 Graver中,是通过在UIView中重写CALayerDelegate的- (void)displayLayer:(CALayer *)layer;方法来接管绘制过程。
如何实现异步绘制成图片的
具体的绘制过程是通过CoreGraphics和 CoreText进行绘制的,UIView的 背景色,背景图片,边框,圆角 使用 CoreGraphics实现,文字图片信息主要使用CoreText进行绘制。
通过Graver我们看下主要的绘制过程,新建LYDrawView继承于UIView
-(void)displayLayer:(LYDrawLayer *)layer {
if (!layer) {
return;
}
[self _displayLayer:layer rect:self.bounds];
}
- (BOOL)drawInRect:(CGRect)rect withContext:(CGContextRef)context asynchronously:(BOOL)asynchronously userInfo:(NSDictionary *)userInfo {
// backgroundColor
CGContextSetFillColorWithColor(context, [UIColor yellowColor].CGColor);
CGContextFillRect(context, rect);
// borderWidth
CGContextAddPath(context, [UIBezierPath bezierPathWithRect:rect].CGPath);
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 0.5);
CGContextDrawPath(context, kCGPathFillStroke);
UIGraphicsPushContext(context);
UIImage *image = [UIImage imageNamed:@"a5a61ab8c196836fe1efbcd9d33edc44"];
[image drawInRect:CGRectInset(rect, 8, 8)];
UIGraphicsPopContext();
return YES;
}
- (void) _displayLayer:(LYDrawLayer *)layer rect:(CGRect) rectToDraw {
BOOL drawInBackground = layer.isAsyncDrawsCurrentContent ;
// 绘制Block
void(^drawBlock)(void) = ^{
CGSize contextSize = layer.bounds.size;
BOOL contextSizeValid = contextSize.width >= 1 && contextSize.height >= 1;
CGContextRef context = NULL;
BOOL drawingFinished = YES;
if (contextSizeValid) {
UIGraphicsBeginImageContextWithOptions(contextSize, layer.isOpaque, layer.contentsScale);
context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
if (rectToDraw.origin.x || rectToDraw.origin.y) {
CGContextTranslateCTM(context, rectToDraw.origin.x, -rectToDraw.origin.y);
}
drawingFinished = [self drawInRect:rectToDraw withContext:context asynchronously:drawInBackground userInfo:@{}];
CGContextRestoreGState(context);
}
if (drawingFinished) {
CGImageRef CGImage = context ? CGBitmapContextCreateImage(context) : NULL;
{
UIImage *image = CGImage ? [UIImage imageWithCGImage:CGImage] : nil;
void (^finishBlock)(void) = ^{
layer.contents = (id)image.CGImage;
};
if (drawInBackground) {
dispatch_async(dispatch_get_main_queue(), finishBlock);
}else {
finishBlock();
}
}
if (CGImage) {
CGImageRelease(CGImage);
}
}
UIGraphicsEndImageContext();
};
if (drawInBackground) {
layer.contents = nil;
dispatch_async(dispatch_get_global_queue(0, 0),drawBlock);
}
}
新建 LYDrawTextView继承于 LYDrawView,重写- (BOOL)drawInRect:(CGRect)rect withContext:(CGContextRef)context asynchronously:(BOOL)asynchronously userInfo:(NSDictionary *)userInfo方法
-(BOOL)drawInRect:(CGRect)rect withContext:(CGContextRef)context asynchronously:(BOOL)asynchronously userInfo:(NSDictionary *)userInfo {
[super drawInRect:rect withContext:context asynchronously:asynchronously userInfo:userInfo];
CGContextTranslateCTM(context, 0, rect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// 创建 绘制的区域
CGMutablePathRef path = CGPathCreateMutable();
CGRect bounds = CGRectInset(rect, 8, 8);
CGPathAddRect(path, NULL, bounds);
// 创建NSMutableString
CFStringRef textString = CFSTR("Hello, World! I know nothing in the world that has as much power as a word. Sometimes I write one, and I look at it, until it begins to shine.");
CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
// 将 textString 复制到 attrString中
CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), textString);
// 创建Color
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[] = {1.0,0.0,0.0,0.8};
CGColorRef red = CGColorCreate(rgbColorSpace, components);
CGColorSpaceRelease(rgbColorSpace);
// 设置前12位的颜色
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 12), kCTForegroundColorAttributeName, red);
// 使用attrString 创建 framesetter
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
CFRelease(attrString);
// 创建ctframe
CTFrameRef ctframe = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
// 在当前context 绘制ctframe
CTFrameDraw(ctframe, context);
CFRelease(ctframe);
CFRelease(path);
CFRelease(framesetter);
return YES;
}
最终的效果图:
图.png
在 Graver中 通过-(BOOL)drawInRect:(CGRect)rect withContext:(CGContextRef)context asynchronously:(BOOL)asynchronously userInfo:(NSDictionary *)userInfo方法向context绘制元素,在所有子类的绘制任务完成时,将context合成一张图片,赋值到layer.contents中。