You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NSAttributedString.draw方法性能问题:绘制耗时过长求助

优化NSAttributedString.draw方法的性能问题

问题描述

我在使用NSAttributedString的draw(with:options:context:)方法时遇到了性能不佳的情况,于是用以下代码测试耗时:

let stopwatch = Stopwatch.createStarted()
if text.length != 0 {
    text.draw(with: textRect, options: .usesLineFragmentOrigin, context: nil)
}
print("stopwatch \(stopwatch.stop().toString())")

测试结果如下:

stopwatch 0.0241029262542725 s
stopwatch 0.0221379995346069 s
stopwatch 0.00433492660522461 s
stopwatch 0.00229907035827637 s
stopwatch 0.00661897659301758 s
stopwatch 0.00540506839752197 s
stopwatch 0.0047069787979126 s
stopwatch 0.00523698329925537 s
stopwatch 0.0541820526123047 s

可以看到text.draw方法的耗时波动较大,部分场景下耗时过高,请问有哪些优化方案?


优化方案

作为常年处理iOS绘制性能问题的开发者,我整理了几个实用的优化方向:

  • 缓存绘制结果(最有效方案之一)
    如果你的文本内容和绘制区域不是频繁变动的,完全可以提前把NSAttributedString绘制到UIImage里,之后直接复用这张图片就行,避免重复调用开销较大的draw方法。示例代码:

    // 提前缓存文本为图片
    private var cachedTextImage: UIImage?
    
    func cacheTextIfNeeded(text: NSAttributedString, targetRect: CGRect) {
        guard cachedTextImage == nil else { return }
        // 用屏幕scale保证图片清晰度
        UIGraphicsBeginImageContextWithOptions(targetRect.size, false, UIScreen.main.scale)
        text.draw(with: targetRect, options: .usesLineFragmentOrigin, context: nil)
        cachedTextImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
    }
    
    // 后续绘制时直接用图片
    func drawText(in rect: CGRect) {
        if let image = cachedTextImage {
            image.draw(in: rect)
        }
    }
    
  • 简化绘制选项
    检查一下是否真的需要.usesLineFragmentOrigin这个选项——它会让文本按照片段换行布局,计算量不小。如果你的文本是单行、或者不需要复杂的换行逻辑,可以去掉这个选项,能显著降低绘制耗时。

  • 优化文本属性与实例复用
    尽量避免给文本设置过多复杂的属性(比如大量不同字体、多层阴影、渐变效果等),这些都会增加排版计算的负担。另外,如果同一文本会被多次绘制,记得复用同一个NSAttributedString实例,不要每次都重新创建。

  • 异步绘制+主线程更新
    如果绘制操作是在主线程执行的,很容易造成UI卡顿。可以把绘制到图片的操作放到后台线程完成,再切回主线程更新UI:

    DispatchQueue.global(qos: .userInitiated).async {
        // 后台绘制图片
        UIGraphicsBeginImageContextWithOptions(textRect.size, false, UIScreen.main.scale)
        self.text.draw(with: textRect, options: .usesLineFragmentOrigin, context: nil)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        DispatchQueue.main.async {
            // 主线程更新UI
            image?.draw(in: self.textRect)
        }
    }
    
  • 检查上下文配置
    如果你自己传入了绘制上下文,确保它的配置是最优的(比如合适的颜色空间、scale)。如果不需要自定义上下文,保持context为nil让系统用默认配置,自定义上下文有时候会带来额外的性能开销。

  • 减少布局计算频率
    如果textRect是频繁变化的,尽量减少计算这个 rect 的次数——每次绘制都需要根据 rect 排版文本,复杂的布局计算也会拖慢整体耗时。


内容的提问来源于stack exchange,提问作者Kimi Chiu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:06:31