经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
iOS两丫技术之UILabel性能不够的解决方法
来源:jb51  时间:2022/8/2 12:45:38  对本文有异议

主要参照 YYKit

YYKit 博大精深,就像少林武功

Async View

为了异步 + runloop 空闲时绘制,

因为苹果的 UILabel 性能不够 6

Async Layer

思路: UI 操作,必须放在主线程,

然而图形处理,可以放在子线程,

( 开辟图形上下文,进行绘制,取出图片 )

最后一步,放在主线程,就好了

layer.contents = image

Custom View 中, layer 类,重新制定为异步 layer

  1. + (Class)layerClass {
  2. return YYAsyncLayer.class;
  3. }

建立绘制任务

创建一个绘制任务,YYAsyncLayerDisplayTask

关键是里面的绘制方法 display

拿到异步图层 layer 创建的图形上下文,

调一下坐标系,( Core Text 的原点,在左下方 )

文本 map 为富文本,

富文本 map 为一帧,

一帧拆分为好多 CTLine,

一行一行地展示

  1. - (YYAsyncLayerDisplayTask *)newAsyncDisplayTask {
  2. // capture current state to display task
  3. NSString *text = _text;
  4. UIFont *fontX = _font;
  5. YYAsyncLayerDisplayTask *task = [YYAsyncLayerDisplayTask new];
  6. CGFloat h_h = self.bounds.size.height;
  7. CGFloat w_w = self.bounds.size.width;
  8. task.display = ^(CGContextRef context, CGSize size, BOOL(^isCancelled)(void)) {
  9. if (isCancelled()) return;
  10. //在这里由于绘制文字会颠倒
  11. [[NSOperationQueue mainQueue] addOperationWithBlock:^{
  12. CGContextSetTextMatrix(context, CGAffineTransformIdentity);
  13. CGContextTranslateCTM(context, 0, h_h);
  14. CGContextScaleCTM(context, 1.0, -1.0);
  15. }];
  16. NSAttributedString* str = [[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName: fontX, NSForegroundColorAttributeName: UIColor.blueColor}];
  17. CTFramesetterRef ref = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)str);
  18. CGPathRef path = CGPathCreateWithRect(CGRectMake(0, 0, w_w, 3000), nil);
  19. CTFrameRef pic = CTFramesetterCreateFrame(ref, CFRangeMake(0, 0), path, nil);
  20. CFArrayRef arr = CTFrameGetLines(pic);
  21. NSArray *array = (__bridge NSArray*)arr;
  22. int i = 0;
  23. int cnt = (int)array.count;
  24. CGPoint originsArray[cnt];
  25. CTFrameGetLineOrigins(pic, CFRangeMake(0, 0), originsArray);
  26. CGFloat y_y = h_h - 60;
  27. while (i < cnt) {
  28. NSLog(@"%f", originsArray[i].y);
  29. CTLineRef line = (__bridge CTLineRef)(array[i]);
  30. CGContextSetTextPosition(context, 0, y_y - i * 30);
  31. CTLineDraw(line, context);
  32. i += 1;
  33. }
  34. };
  35. return task;
  36. }

Async Layer 中, 启动绘制任务,

先处理下继承关系,

再执行上文提到的绘制任务

  1. - (void)display {
  2. super.contents = super.contents;
  3. [self _displayAsync];
  4. }

执行绘制任务,

拿到任务,没有绘制内容,就算了

再判断,自身的大小 ( size ), 合不合规

大小 CGSize(1, 1), 就继续,

子线程,先开辟图形上下文,

再处理背景色,

如果顺利,执行上文的绘制步骤,

从图形上下文中,取出 image, 交给 layer.contents

  1. - (void)_displayAsync{
  2. __strong id<YYAsyncLayerDelegate> delegate = (id)self.delegate;
  3. YYAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
  4. if (!task.display) {
  5. self.contents = nil;
  6. return;
  7. }
  8. CGSize size = self.bounds.size;
  9. BOOL opaque = self.opaque;
  10. CGFloat scale = self.contentsScale;
  11. CGColorRef backgroundColor = (opaque && self.backgroundColor) ? CGColorRetain(self.backgroundColor) : NULL;
  12. if (size.width < 1 || size.height < 1) {
  13. CGImageRef image = (__bridge_retained CGImageRef)(self.contents);
  14. self.contents = nil;
  15. if (image) {
  16. dispatch_async(YYAsyncLayerGetReleaseQueue(), ^{
  17. CFRelease(image);
  18. });
  19. }
  20. CGColorRelease(backgroundColor);
  21. return;
  22. }
  23. dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{
  24. if (isCancelled()) {
  25. CGColorRelease(backgroundColor);
  26. return;
  27. }
  28. UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
  29. CGContextRef context = UIGraphicsGetCurrentContext();
  30. if (opaque) {
  31. CGContextSaveGState(context); {
  32. if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) {
  33. CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
  34. CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
  35. CGContextFillPath(context);
  36. }
  37. if (backgroundColor) {
  38. CGContextSetFillColorWithColor(context, backgroundColor);
  39. CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
  40. CGContextFillPath(context);
  41. }
  42. } CGContextRestoreGState(context);
  43. CGColorRelease(backgroundColor);
  44. }
  45. task.display(context, size, isCancelled);
  46. if (isCancelled()) {
  47. UIGraphicsEndImageContext();
  48. return;
  49. }
  50. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  51. UIGraphicsEndImageContext();
  52. if (isCancelled()) {
  53. return;
  54. }
  55. dispatch_async(dispatch_get_main_queue(), ^{
  56. if (isCancelled() == NO) {
  57. self.contents = (__bridge id)(image.CGImage);
  58. }
  59. });
  60. });
  61. }

RunLoop

触发

设置样式后,不会立即触发,重绘

先保存起来

  1. - (void)setText:(NSString *)text {
  2. _text = text.copy;
  3. [[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];
  4. }

调用异步图层的绘制任务

  1. - (void)contentsNeedUpdated {
  2. // do update
  3. [self.layer setNeedsDisplay];
  4. }

事件的保存

先把方法调用的 target 和 action, 保存为对象

YYTransactionSetup(); 单例方法,初始化

把方法调用的对象,添加到集合

  1. @implementation YYTransaction
  2. + (YYTransaction *)transactionWithTarget:(id)target selector:(SEL)selector{
  3. if (!target || !selector) return nil;
  4. YYTransaction *t = [YYTransaction new];
  5. t.target = target;
  6. t.selector = selector;
  7. return t;
  8. }
  9. - (void)commit {
  10. if (!_target || !_selector) return;
  11. YYTransactionSetup();
  12. [transactionSet addObject:self];
  13. }

空闲的时候,把事情给办了,不影响帧率

下面的单例方法,初始化事件任务集合,

run loop 回调中,执行

不干涉, 主 runloop

  1. static NSMutableSet *transactionSet = nil;
  2. static void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
  3. if (transactionSet.count == 0) return;
  4. NSSet *currentSet = transactionSet;
  5. transactionSet = [NSMutableSet new];
  6. [currentSet enumerateObjectsUsingBlock:^(YYTransaction *transaction, BOOL *stop) {
  7. [transaction.target performSelector:transaction.selector];
  8. }];
  9. }
  10. static void YYTransactionSetup() {
  11. static dispatch_once_t onceToken;
  12. dispatch_once(&onceToken, ^{
  13. transactionSet = [NSMutableSet new];
  14. CFRunLoopRef runloop = CFRunLoopGetMain();
  15. CFRunLoopObserverRef observer;
  16. observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(),
  17. kCFRunLoopBeforeWaiting | kCFRunLoopExit,
  18. true,
  19. 0xFFFFFF,
  20. YYRunLoopObserverCallBack, NULL);
  21. CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
  22. CFRelease(observer);
  23. });
  24. }

YYLabel

功能相当强大的渲染工具,

在上文异步渲染的基础上

支持各种样式

增加了一层抽象 YYTextLayout

YYLabel 中的绘制任务,

如果需要更新,就创建新的布局 layout ,

如果居中 / 底部对其,处理下 layout 布局

然后 layout 绘制

  1. @implementation YYLabel
  2. - (YYTextAsyncLayerDisplayTask *)newAsyncDisplayTask {
  3. // create display task
  4. YYTextAsyncLayerDisplayTask *task = [YYTextAsyncLayerDisplayTask new];
  5. task.display = ^(CGContextRef context, CGSize size, BOOL (^isCancelled)(void)) {
  6. if (isCancelled()) return;
  7. if (text.length == 0) return;
  8. YYTextLayout *drawLayout = layout;
  9. if (layoutNeedUpdate) {
  10. layout = [YYTextLayout layoutWithContainer:container text:text];
  11. shrinkLayout = [YYLabel _shrinkLayoutWithLayout:layout];
  12. if (isCancelled()) return;
  13. layoutUpdated = YES;
  14. drawLayout = shrinkLayout ? shrinkLayout : layout;
  15. }
  16. CGSize boundingSize = drawLayout.textBoundingSize;
  17. CGPoint point = CGPointZero;
  18. if (verticalAlignment == YYTextVerticalAlignmentCenter) {
  19. if (drawLayout.container.isVerticalForm) {
  20. point.x = -(size.width - boundingSize.width) * 0.5;
  21. } else {
  22. point.y = (size.height - boundingSize.height) * 0.5;
  23. }
  24. } else if (verticalAlignment == YYTextVerticalAlignmentBottom) {
  25. if (drawLayout.container.isVerticalForm) {
  26. point.x = -(size.width - boundingSize.width);
  27. } else {
  28. point.y = (size.height - boundingSize.height);
  29. }
  30. }
  31. point = YYTextCGPointPixelRound(point);
  32. [drawLayout drawInContext:context size:size point:point view:nil layer:nil debug:debug cancel:isCancelled];
  33. };
  34. return task;
  35. }
  36. @end

绘制各种

先绘制背景,

其次画阴影,

下划线,

文字,

图片

边框

  1. @implementation YYTextLayout
  2. - (void)drawInContext:(CGContextRef)context
  3. size:(CGSize)size
  4. point:(CGPoint)point
  5. view:(UIView *)view
  6. layer:(CALayer *)layer
  7. debug:(YYTextDebugOption *)debug
  8. cancel:(BOOL (^)(void))cancel{
  9. @autoreleasepool {
  10. if (self.needDrawBlockBorder && context) {
  11. if (cancel && cancel()) return;
  12. YYTextDrawBlockBorder(self, context, size, point, cancel);
  13. }
  14. if (self.needDrawBackgroundBorder && context) {
  15. if (cancel && cancel()) return;
  16. YYTextDrawBorder(self, context, size, point, YYTextBorderTypeBackgound, cancel);
  17. }
  18. if (self.needDrawShadow && context) {
  19. if (cancel && cancel()) return;
  20. YYTextDrawShadow(self, context, size, point, cancel);
  21. }
  22. if (self.needDrawUnderline && context) {
  23. if (cancel && cancel()) return;
  24. YYTextDrawDecoration(self, context, size, point, YYTextDecorationTypeUnderline, cancel);
  25. }
  26. if (self.needDrawText && context) {
  27. if (cancel && cancel()) return;
  28. YYTextDrawText(self, context, size, point, cancel);
  29. }
  30. if (self.needDrawAttachment && (context || view || layer)) {
  31. if (cancel && cancel()) return;
  32. YYTextDrawAttachment(self, context, size, point, view, layer, cancel);
  33. }
  34. if (self.needDrawInnerShadow && context) {
  35. if (cancel && cancel()) return;
  36. YYTextDrawInnerShadow(self, context, size, point, cancel);
  37. }
  38. if (self.needDrawStrikethrough && context) {
  39. if (cancel && cancel()) return;
  40. YYTextDrawDecoration(self, context, size, point, YYTextDecorationTypeStrikethrough, cancel);
  41. }
  42. if (self.needDrawBorder && context) {
  43. if (cancel && cancel()) return;
  44. YYTextDrawBorder(self, context, size, point, YYTextBorderTypeNormal, cancel);
  45. }
  46. if (debug.needDrawDebug && context) {
  47. if (cancel && cancel()) return;
  48. YYTextDrawDebug(self, context, size, point, debug);
  49. }
  50. }
  51. }

进入绘制文字

还有图片

这里的绘制粒度,比较上文,

粒度更加的细

上文是 CTLine,

这里是 CTRun

  1. // 注意条件判断,
  2. // 与保存 / 恢复图形上下文
  3. static void YYTextDrawAttachment(YYTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, UIView *targetView, CALayer *targetLayer, BOOL (^cancel)(void)) {
  4. BOOL isVertical = layout.container.verticalForm;
  5. CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0;
  6. for (NSUInteger i = 0, max = layout.attachments.count; i < max; i++) {
  7. YYTextAttachment *a = layout.attachments[i];
  8. if (!a.content) continue;
  9. UIImage *image = nil;
  10. UIView *view = nil;
  11. CALayer *layer = nil;
  12. if ([a.content isKindOfClass:[UIImage class]]) {
  13. image = a.content;
  14. } else if ([a.content isKindOfClass:[UIView class]]) {
  15. view = a.content;
  16. } else if ([a.content isKindOfClass:[CALayer class]]) {
  17. layer = a.content;
  18. }
  19. if (!image && !view && !layer) continue;
  20. if (image && !context) continue;
  21. if (view && !targetView) continue;
  22. if (layer && !targetLayer) continue;
  23. if (cancel && cancel()) break;
  24. CGSize asize = image ? image.size : view ? view.frame.size : layer.frame.size;
  25. CGRect rect = ((NSValue *)layout.attachmentRects[i]).CGRectValue;
  26. if (isVertical) {
  27. rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(a.contentInsets));
  28. } else {
  29. rect = UIEdgeInsetsInsetRect(rect, a.contentInsets);
  30. }
  31. rect = YYTextCGRectFitWithContentMode(rect, asize, a.contentMode);
  32. rect = YYTextCGRectPixelRound(rect);
  33. rect = CGRectStandardize(rect);
  34. rect.origin.x += point.x + verticalOffset;
  35. rect.origin.y += point.y;
  36. if (image) {
  37. CGImageRef ref = image.CGImage;
  38. if (ref) {
  39. CGContextSaveGState(context);
  40. CGContextTranslateCTM(context, 0, CGRectGetMaxY(rect) + CGRectGetMinY(rect));
  41. CGContextScaleCTM(context, 1, -1);
  42. CGContextDrawImage(context, rect, ref);
  43. CGContextRestoreGState(context);
  44. }
  45. } else if (view) {
  46. view.frame = rect;
  47. [targetView addSubview:view];
  48. } else if (layer) {
  49. layer.frame = rect;
  50. [targetLayer addSublayer:layer];
  51. }
  52. }
  53. }

本文,最后一个问题:

上面 layout 的绘制信息,怎么取得的?

先拿文本,创建 CTFrame, CTFrame 中拿到 CTLine 数组

然后遍历每一行,与计算

  1. @implementation YYTextLayout
  2. + (YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range {
  3. // ...
  4. ctSetter = CTFramesetterCreateWithAttributedString((CFTypeRef)text);
  5. if (!ctSetter) goto fail;
  6. ctFrame = CTFramesetterCreateFrame(ctSetter, YYTextCFRangeFromNSRange(range), cgPath, (CFTypeRef)frameAttrs);
  7. if (!ctFrame) goto fail;
  8. lines = [NSMutableArray new];
  9. ctLines = CTFrameGetLines(ctFrame);
  10. // ...
  11. for (NSUInteger i = 0, max = lines.count; i < max; i++) {
  12. YYTextLine *line = lines[i];
  13. if (truncatedLine && line.index == truncatedLine.index) line = truncatedLine;
  14. if (line.attachments.count > 0) {
  15. [attachments addObjectsFromArray:line.attachments];
  16. [attachmentRanges addObjectsFromArray:line.attachmentRanges];
  17. [attachmentRects addObjectsFromArray:line.attachmentRects];
  18. for (YYTextAttachment *attachment in line.attachments) {
  19. if (attachment.content) {
  20. [attachmentContentsSet addObject:attachment.content];
  21. }
  22. }
  23. }
  24. }
  25. // ...
  26. }

github repo

到此这篇关于iOS两丫技术之UILabel性能不够的解决方法的文章就介绍到这了,更多相关iOS UILabe内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号