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

