欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

iOS两丫技术之UILabel性能不够的解决方法,

来源: 开发者 投稿于  被查看 26039 次 评论:269

iOS两丫技术之UILabel性能不够的解决方法,


目录
  • Async View
    • Async Layer
    • RunLoop
      • 触发
      • 事件的保存
  • YYLabel

    主要参照 YYKit

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

    Async View

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

    因为苹果的 UILabel 性能不够 6

    Async Layer

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

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

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

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

    layer.contents = image

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

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

    建立绘制任务

    创建一个绘制任务,YYAsyncLayerDisplayTask

    关键是里面的绘制方法 display

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

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

    文本 map 为富文本,

    富文本 map 为一帧,

    一帧拆分为好多 CTLine,

    一行一行地展示

    - (YYAsyncLayerDisplayTask *)newAsyncDisplayTask {
        // capture current state to display task
        NSString *text = _text;
        UIFont *fontX = _font;
        YYAsyncLayerDisplayTask *task = [YYAsyncLayerDisplayTask new];
        CGFloat h_h = self.bounds.size.height;
        CGFloat w_w = self.bounds.size.width;
        task.display = ^(CGContextRef context, CGSize size, BOOL(^isCancelled)(void)) {
            if (isCancelled()) return;
            //在这里由于绘制文字会颠倒
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                CGContextSetTextMatrix(context, CGAffineTransformIdentity);
                CGContextTranslateCTM(context, 0, h_h);
                CGContextScaleCTM(context, 1.0, -1.0);
            }];
            NSAttributedString* str = [[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName: fontX, NSForegroundColorAttributeName: UIColor.blueColor}];
            CTFramesetterRef ref = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)str);
            CGPathRef path = CGPathCreateWithRect(CGRectMake(0, 0, w_w, 3000), nil);
            CTFrameRef pic = CTFramesetterCreateFrame(ref, CFRangeMake(0, 0), path, nil);
            CFArrayRef arr = CTFrameGetLines(pic);
            NSArray *array = (__bridge NSArray*)arr;
            int i = 0;
            int cnt = (int)array.count;
            CGPoint originsArray[cnt];
            CTFrameGetLineOrigins(pic, CFRangeMake(0, 0), originsArray);
            CGFloat y_y = h_h - 60;
            while (i < cnt) {
                NSLog(@"%f", originsArray[i].y);
                CTLineRef line = (__bridge CTLineRef)(array[i]);
                CGContextSetTextPosition(context, 0, y_y - i * 30);
                CTLineDraw(line, context);
                i += 1;
            }
        };
        return task;
    }

    Async Layer 中, 启动绘制任务,

    先处理下继承关系,

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

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

    执行绘制任务,

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

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

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

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

    再处理背景色,

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

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

    - (void)_displayAsync{
        __strong id<YYAsyncLayerDelegate> delegate = (id)self.delegate;
        YYAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
        if (!task.display) {
            self.contents = nil;
            return;
        }
            CGSize size = self.bounds.size;
            BOOL opaque = self.opaque;
            CGFloat scale = self.contentsScale;
            CGColorRef backgroundColor = (opaque && self.backgroundColor) ? CGColorRetain(self.backgroundColor) : NULL;
            if (size.width < 1 || size.height < 1) {
                CGImageRef image = (__bridge_retained CGImageRef)(self.contents);
                self.contents = nil;
                if (image) {
                    dispatch_async(YYAsyncLayerGetReleaseQueue(), ^{
                        CFRelease(image);
                    });
                }
                CGColorRelease(backgroundColor);
                return;
            }
            dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{
                if (isCancelled()) {
                    CGColorRelease(backgroundColor);
                    return;
                }
                UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
                CGContextRef context = UIGraphicsGetCurrentContext();
                if (opaque) {
                    CGContextSaveGState(context); {
                        if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) {
                            CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
                            CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
                            CGContextFillPath(context);
                        }
                        if (backgroundColor) {
                            CGContextSetFillColorWithColor(context, backgroundColor);
                            CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
                            CGContextFillPath(context);
                        }
                    } CGContextRestoreGState(context);
                    CGColorRelease(backgroundColor);
                }
                task.display(context, size, isCancelled);
                if (isCancelled()) {
                    UIGraphicsEndImageContext();
                    return;
                }
                UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
                UIGraphicsEndImageContext();
                if (isCancelled()) {
                    return;
                }
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (isCancelled() == NO) {
                        self.contents = (__bridge id)(image.CGImage);
                    }
                });
            });
    }

    RunLoop

    触发

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

    先保存起来

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

    调用异步图层的绘制任务

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

    事件的保存

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

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

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

    @implementation YYTransaction
    + (YYTransaction *)transactionWithTarget:(id)target selector:(SEL)selector{
        if (!target || !selector) return nil;
        YYTransaction *t = [YYTransaction new];
        t.target = target;
        t.selector = selector;
        return t;
    }
    - (void)commit {
        if (!_target || !_selector) return;
        YYTransactionSetup();
        [transactionSet addObject:self];
    }

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

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

    run loop 回调中,执行

    不干涉, 主 runloop

    static NSMutableSet *transactionSet = nil;
    static void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
        if (transactionSet.count == 0) return;
        NSSet *currentSet = transactionSet;
        transactionSet = [NSMutableSet new];
        [currentSet enumerateObjectsUsingBlock:^(YYTransaction *transaction, BOOL *stop) {
            [transaction.target performSelector:transaction.selector];
        }];
    }
    static void YYTransactionSetup() {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            transactionSet = [NSMutableSet new];
            CFRunLoopRef runloop = CFRunLoopGetMain();
            CFRunLoopObserverRef observer;
            observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(),
                                               kCFRunLoopBeforeWaiting | kCFRunLoopExit,
                                               true, 
                                               0xFFFFFF,
                                               YYRunLoopObserverCallBack, NULL);
            CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
            CFRelease(observer);
        });
    }

    YYLabel

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

    在上文异步渲染的基础上

    支持各种样式

    增加了一层抽象 YYTextLayout

    YYLabel 中的绘制任务,

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

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

    然后 layout 绘制

    @implementation YYLabel
    - (YYTextAsyncLayerDisplayTask *)newAsyncDisplayTask {
        // create display task
        YYTextAsyncLayerDisplayTask *task = [YYTextAsyncLayerDisplayTask new];
        task.display = ^(CGContextRef context, CGSize size, BOOL (^isCancelled)(void)) {
            if (isCancelled()) return;
            if (text.length == 0) return;
            YYTextLayout *drawLayout = layout;
            if (layoutNeedUpdate) {
                layout = [YYTextLayout layoutWithContainer:container text:text];
                shrinkLayout = [YYLabel _shrinkLayoutWithLayout:layout];
                if (isCancelled()) return;
                layoutUpdated = YES;
                drawLayout = shrinkLayout ? shrinkLayout : layout;
            }
            CGSize boundingSize = drawLayout.textBoundingSize;
            CGPoint point = CGPointZero;
            if (verticalAlignment == YYTextVerticalAlignmentCenter) {
                if (drawLayout.container.isVerticalForm) {
                    point.x = -(size.width - boundingSize.width) * 0.5;
                } else {
                    point.y = (size.height - boundingSize.height) * 0.5;
                }
            } else if (verticalAlignment == YYTextVerticalAlignmentBottom) {
                if (drawLayout.container.isVerticalForm) {
                    point.x = -(size.width - boundingSize.width);
                } else {
                    point.y = (size.height - boundingSize.height);
                }
            }
            point = YYTextCGPointPixelRound(point);
            [drawLayout drawInContext:context size:size point:point view:nil layer:nil debug:debug cancel:isCancelled];
        };
        return task;
    }
    @end

    绘制各种

    先绘制背景,

    其次画阴影,

    下划线,

    文字,

    图片

    边框

    @implementation YYTextLayout
    - (void)drawInContext:(CGContextRef)context
                     size:(CGSize)size
                    point:(CGPoint)point
                     view:(UIView *)view
                    layer:(CALayer *)layer
                    debug:(YYTextDebugOption *)debug
                    cancel:(BOOL (^)(void))cancel{
        @autoreleasepool {
            if (self.needDrawBlockBorder && context) {
                if (cancel && cancel()) return;
                YYTextDrawBlockBorder(self, context, size, point, cancel);
            }
            if (self.needDrawBackgroundBorder && context) {
                if (cancel && cancel()) return;
                YYTextDrawBorder(self, context, size, point, YYTextBorderTypeBackgound, cancel);
            }
            if (self.needDrawShadow && context) {
                if (cancel && cancel()) return;
                YYTextDrawShadow(self, context, size, point, cancel);
            }
            if (self.needDrawUnderline && context) {
                if (cancel && cancel()) return;
                YYTextDrawDecoration(self, context, size, point, YYTextDecorationTypeUnderline, cancel);
            }
            if (self.needDrawText && context) {
                if (cancel && cancel()) return;
                YYTextDrawText(self, context, size, point, cancel);
            }
            if (self.needDrawAttachment && (context || view || layer)) {
                if (cancel && cancel()) return;
                YYTextDrawAttachment(self, context, size, point, view, layer, cancel);
            }
            if (self.needDrawInnerShadow && context) {
                if (cancel && cancel()) return;
                YYTextDrawInnerShadow(self, context, size, point, cancel);
            }
            if (self.needDrawStrikethrough && context) {
                if (cancel && cancel()) return;
                YYTextDrawDecoration(self, context, size, point, YYTextDecorationTypeStrikethrough, cancel);
            }
            if (self.needDrawBorder && context) {
                if (cancel && cancel()) return;
                YYTextDrawBorder(self, context, size, point, YYTextBorderTypeNormal, cancel);
            }
            if (debug.needDrawDebug && context) {
                if (cancel && cancel()) return;
                YYTextDrawDebug(self, context, size, point, debug);
            }
        }
    }

    进入绘制文字

    还有图片

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

    粒度更加的细

    上文是 CTLine,

    这里是 CTRun

    // 注意条件判断,
    // 与保存 / 恢复图形上下文
    static void YYTextDrawAttachment(YYTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, UIView *targetView, CALayer *targetLayer, BOOL (^cancel)(void)) {
        BOOL isVertical = layout.container.verticalForm;
        CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0;
        for (NSUInteger i = 0, max = layout.attachments.count; i < max; i++) {
            YYTextAttachment *a = layout.attachments[i];
            if (!a.content) continue;
            UIImage *image = nil;
            UIView *view = nil;
            CALayer *layer = nil;
            if ([a.content isKindOfClass:[UIImage class]]) {
                image = a.content;
            } else if ([a.content isKindOfClass:[UIView class]]) {
                view = a.content;
            } else if ([a.content isKindOfClass:[CALayer class]]) {
                layer = a.content;
            }
            if (!image && !view && !layer) continue;
            if (image && !context) continue;
            if (view && !targetView) continue;
            if (layer && !targetLayer) continue;
            if (cancel && cancel()) break;
            CGSize asize = image ? image.size : view ? view.frame.size : layer.frame.size;
            CGRect rect = ((NSValue *)layout.attachmentRects[i]).CGRectValue;
            if (isVertical) {
                rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(a.contentInsets));
            } else {
                rect = UIEdgeInsetsInsetRect(rect, a.contentInsets);
            }
            rect = YYTextCGRectFitWithContentMode(rect, asize, a.contentMode);
            rect = YYTextCGRectPixelRound(rect);
            rect = CGRectStandardize(rect);
            rect.origin.x += point.x + verticalOffset;
            rect.origin.y += point.y;
            if (image) {
                CGImageRef ref = image.CGImage;
                if (ref) {
                    CGContextSaveGState(context);
                    CGContextTranslateCTM(context, 0, CGRectGetMaxY(rect) + CGRectGetMinY(rect));
                    CGContextScaleCTM(context, 1, -1);
                    CGContextDrawImage(context, rect, ref);
                    CGContextRestoreGState(context);
                }
            } else if (view) {
                view.frame = rect;
                [targetView addSubview:view];
            } else if (layer) {
                layer.frame = rect;
                [targetLayer addSublayer:layer];
            }
        }
    }

    本文,最后一个问题:

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

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

    然后遍历每一行,与计算

    @implementation YYTextLayout
    + (YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range {
        // ...
        ctSetter = CTFramesetterCreateWithAttributedString((CFTypeRef)text);
        if (!ctSetter) goto fail;
        ctFrame = CTFramesetterCreateFrame(ctSetter, YYTextCFRangeFromNSRange(range), cgPath, (CFTypeRef)frameAttrs);
        if (!ctFrame) goto fail;
        lines = [NSMutableArray new];
        ctLines = CTFrameGetLines(ctFrame);
       // ...
       for (NSUInteger i = 0, max = lines.count; i < max; i++) {
            YYTextLine *line = lines[i];
            if (truncatedLine && line.index == truncatedLine.index) line = truncatedLine;
            if (line.attachments.count > 0) {
                [attachments addObjectsFromArray:line.attachments];
                [attachmentRanges addObjectsFromArray:line.attachmentRanges];
                [attachmentRects addObjectsFromArray:line.attachmentRects];
                for (YYTextAttachment *attachment in line.attachments) {
                    if (attachment.content) {
                        [attachmentContentsSet addObject:attachment.content];
                    }
                }
            }
        }
        // ...
    }

    github repo

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

    您可能感兴趣的文章:
    • iOS基于 UILabel实现文字添加描边功能
    • iOS如何封装带复制功能的UILabel示例代码
    • iOS开发总结之UILabel常用属性介绍
    • iOS中UILabel设置居上对齐、居中对齐、居下对齐及文字置顶显示
    • iOS动态调整UILabel高度的几种方法
    • iOS UILabel 设置内容的间距及高度的计算示例
    • iOS中UILabel实现长按复制功能实例代码
    • IOS 开发之UILabel 或者 UIButton加下划线链接
    • iOS UILabel根据内容自动调整高度

    用户评论