YYText源码解读-YYText同步/异步渲染流程(二)

[文章转载自40K CLUB APP]

一、涉及到的几个类

  • YYLabel:Label控件,继承UIView
  • YYTextLayout、YYTextContainer、YYTextLine:用于布局计算,它是YYLabel的属性,当我们为YYLabel设置属性时(比如text、textColor等),它会计算出布局信息和完成内容的绘制。
  • YYTextAsyncLayer:自定义的Layer组件

二、流程

1 将YYLabel的默认Layer替换为YYTextAsyncLayer。

从上一篇文章中得知,我们只需重写+layerClass方法即可。

1 + (Class)layerClass {
2 return [YYTextAsyncLayer class];
3 }

2 重写YYTextAsyncLayer的display方法

1 - (void)display {
2 super.contents = super.contents;
3 // _displaysAsynchronously属性表示是否需要异步绘制,默认是NO
4 [self _displayAsync:_displaysAsynchronously];
5 }

从上一篇文章中得知,display方法用来设置contents属性,而我们可以通过为contents赋一个CGImage的值,将YYLabel的结果,显示出来。这就需要我们通过YYLabel的所有属性(比如text、textColor等)来绘制一个CGImage出来,绘制的动作就是通过下面的方法完成的,绘制可以同步进行(默认),也可以异步进行,提高性能。

1 - (void)_displayAsync:(BOOL)async {
2 // 暂时省略内容,后续后逐行分析
3 }

要想生存内容图片,我们需要知道布局信息。

布局信息的计算是通过YYLabel的YYTextLayout属性完成的。我们知道Layer的delegate是它的View(即YYTextAsyncLayer的delegate是YYLabel),我们为YYLabel实现一个newAsyncDisplayTask方法,YYTextAsyncLayer通过delegate调用该方法会返回一个YYTextAsyncLayerDisplayTask对象。通过YYTextAsyncLayerDisplayTask对象的block属性,YYTextAsyncLayer将上写文(context)回调给YYLabel的YYTextLayout属性,这时YYTextLayout的布局信息已经计算好,又获得了上下文信息(context),就可以完成绘制了。

3 YYTextLayout的布局计算方法和绘制方法

(1)布局计算方法

1 + (YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range;

(2)绘制方法

1 - (void)drawInContext:(nullable CGContextRef)context
2 size:(CGSize)size
3 point:(CGPoint)point
4 view:(nullable UIView *)view
5 layer:(nullable CALayer *)layer
6 debug:(nullable YYTextDebugOption *)debug
7 cancel:(nullable BOOL (^)(void))cancel;

4 YYTextLayout绘制好图片,YYTextAsyncLayer通过上下文(context)就能拿到图片,并设置给contents属性,这样整个Label的显示就完成了。

最后看一下YYTextAsyncLayer的生成图片的方法

  1 - (void)_displayAsync:(BOOL)async {
  2 __strong id<YYTextAsyncLayerDelegate> delegate = (id)self.delegate;
  3 YYTextAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
  4 if (!task.display) {
  5 if (task.willDisplay) task.willDisplay(self);
  6 self.contents = nil;
  7 if (task.didDisplay) task.didDisplay(self, YES);
  8 return;
  9 }
 10 
 11 if (async) {
 12 if (task.willDisplay) task.willDisplay(self);
 13 _YYTextSentinel *sentinel = _sentinel;
 14 int32_t value = sentinel.value;
 15 BOOL (^isCancelled)() = ^BOOL() {
 16 return value != sentinel.value;
 17 };
 18 CGSize size = self.bounds.size;
 19 BOOL opaque = self.opaque;
 20 CGFloat scale = self.contentsScale;
 21 CGColorRef backgroundColor = (opaque && self.backgroundColor) ? CGColorRetain(self.backgroundColor) : NULL;
 22 if (size.width < 1 || size.height < 1) {
 23 CGImageRef image = (__bridge_retained CGImageRef)(self.contents);
 24 self.contents = nil;
 25 if (image) {
 26 dispatch_async(YYTextAsyncLayerGetReleaseQueue(), ^{
 27 CFRelease(image);
 28 });
 29 }
 30 if (task.didDisplay) task.didDisplay(self, YES);
 31 CGColorRelease(backgroundColor);
 32 return;
 33 }
 34 
 35 dispatch_async(YYTextAsyncLayerGetDisplayQueue(), ^{
 36 if (isCancelled()) {
 37 CGColorRelease(backgroundColor);
 38 return;
 39 }
 40 UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
 41 CGContextRef context = UIGraphicsGetCurrentContext();
 42 if (opaque) {
 43 CGContextSaveGState(context); {
 44 if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) {
 45 CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
 46 CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
 47 CGContextFillPath(context);
 48 }
 49 if (backgroundColor) {
 50 CGContextSetFillColorWithColor(context, backgroundColor);
 51 CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
 52 CGContextFillPath(context);
 53 }
 54 } CGContextRestoreGState(context);
 55 CGColorRelease(backgroundColor);
 56 }
 57 task.display(context, size, isCancelled);
 58 if (isCancelled()) {
 59 UIGraphicsEndImageContext();
 60 dispatch_async(dispatch_get_main_queue(), ^{
 61 if (task.didDisplay) task.didDisplay(self, NO);
 62 });
 63 return;
 64 }
 65 UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
 66 UIGraphicsEndImageContext();
 67 if (isCancelled()) {
 68 dispatch_async(dispatch_get_main_queue(), ^{
 69 if (task.didDisplay) task.didDisplay(self, NO);
 70 });
 71 return;
 72 }
 73 dispatch_async(dispatch_get_main_queue(), ^{
 74 if (isCancelled()) {
 75 if (task.didDisplay) task.didDisplay(self, NO);
 76 } else {
 77 self.contents = (__bridge id)(image.CGImage);
 78 if (task.didDisplay) task.didDisplay(self, YES);
 79 }
 80 });
 81 });
 82 } else {
 83 [_sentinel increase];
 84 if (task.willDisplay) task.willDisplay(self);
 85 UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, self.contentsScale);
 86 CGContextRef context = UIGraphicsGetCurrentContext();
 87 if (self.opaque) {
 88 CGSize size = self.bounds.size;
 89 size.width *= self.contentsScale;
 90 size.height *= self.contentsScale;
 91 CGContextSaveGState(context); {
 92 if (!self.backgroundColor || CGColorGetAlpha(self.backgroundColor) < 1) {
 93 CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
 94 CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));
 95 CGContextFillPath(context);
 96 }
 97 if (self.backgroundColor) {
 98 CGContextSetFillColorWithColor(context, self.backgroundColor);
 99 CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));
100 CGContextFillPath(context);
101 }
102 } CGContextRestoreGState(context);
103 }
104 task.display(context, self.bounds.size, ^{return NO;});
105 UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
106 UIGraphicsEndImageContext();
107 self.contents = (__bridge id)(image.CGImage);
108 if (task.didDisplay) task.didDisplay(self, YES);
109 }
110 }

[文章转载自40K CLUB APP]

原文地址:https://www.cnblogs.com/fkclub/p/12402085.html