使用 Time Profiler 查看关键函数调用耗时情况,从而分析和解决问题

swift 复制代码
1.59 s   15.4%	1.31 s	 	    closure #6 in LineChartView.generateSingleLineChart(from:)
579.00 ms    5.6%	490.00 ms	 	    closure #6 in LineChartView.generateGroupLineChart(from:)
524.00 ms    5.0%	423.00 ms	 	    closure #1 in closure #4 in LineChartView.generateLineChart(from:)
269.00 ms    2.6%	221.00 ms	 	    closure #1 in closure #6 in LineChartView.generateOtherLineChart(from:)
92.00 ms    0.8%	69.00 ms	 	    closure #1 in closure #1 in closure #4 in LineChartView.ChartSectionView.body.getter


1.06 s   21.2%	884.00 ms	 	    closure #6 in LineChartView.generateSingleLineChart(from:)

	let chartContent = ForEach(data) { model in
            // LineMark for data
226 ms            LineMark(
43 ms                x: .value("Time", model.timestamp),
49 ms                y: .value("Value", model.value)
            )
166 ms            .foregroundStyle(viewModel.getLineColor(for: model.name))
561 ms            .lineStyle(StrokeStyle(lineWidth: chartLineWidth, lineCap: .round, lineJoin: .round))
        }

325.00 ms    6.5%	259.00 ms	 	    closure #1 in closure #4 in LineChartView.generateLineChart(from:)
			return Chart {
            ForEach(data) { model in
102 ms            LineMark(
18 ms               x: .value("Time", model.timestamp),
18 ms                y: .value("Value", model.value)
                )
185 ms              .lineStyle(StrokeStyle(lineWidth: chartLineWidth, lineCap: .round, lineJoin: .round))
            }
        }

306.00 ms    6.1%	261.00 ms	 	    closure #6 in LineChartView.generateGroupLineChart(from:)

let chartContent = ForEach(data) { model in
            // LineMark for data
58 ms             LineMark(
10 ms               x: .value("Time", model.timestamp),
6 ms               y: .value("Value", model.value)
            )
67 ms            .foregroundStyle(by: .value("Field", model.name))
158 ms              .lineStyle(StrokeStyle(lineWidth: chartLineWidth, lineCap: .round, lineJoin: .round))
        }
144.00 ms    2.8%	123.00 ms	 	    closure #1 in closure #6 in LineChartView.generateOtherLineChart(from:)

return Chart {
            ForEach(data) { model in
25 ms             LineMark(
7 ms             x: .value("Time", model.timestamp),
6 ms             y: .value("Value", model.value)
                )
23 ms            .foregroundStyle(model.color)
77 ms             .lineStyle(StrokeStyle(lineWidth: chartLineWidth, lineCap: .round, lineJoin: .round))
            }
        }
42.00 ms    0.8%	42.00 ms	 	    initializeWithCopy for LineChartData

1. 分析 Time Profiler 的耗时情况

  • 闭包内部耗时较高

    • generateSingleLineChart 中,ForEach 循环内部的闭包花费了 884 毫秒,其中 .lineStyle(...) 调用占了 561 毫秒,说明对每个数据点的样式计算非常耗时。
    • 同理,generateLineChartgenerateGroupLineChart 中,也分别花费了 259 毫秒和 261 毫秒。这表明在遍历数据并生成 LineMark 时,SwiftUI 在计算各个修饰符(如 lineStyleforegroundStyle)的过程中开销较大。
  • 重复计算和视图重构

    • 每次调用这些生成图表的函数时,都需要重新计算 x 轴、y 轴的最小值、最大值、刻度值等,而且对每个数据点都要构建一个新的 LineMark。如果数据点较多,这些计算和视图构造操作叠加起来就会变得非常耗时。
  • 样式计算问题

    • 例如在 generateSingleLineChart 中,对每个数据点调用 viewModel.getLineColor(for:) 和创建 StrokeStyle 都消耗了大量时间。这提示我们,样式的计算和生成可能没有复用,每次都在重新构建。

  • 重绘整个视图
    • 当从 LineChartView 跳转到子页面后返回,SwiftUI 会重新调用 body 生成视图。即使数据没有改变,所有的闭包(如 generateSingleLineChartgenerateGroupLineChartgenerateLineChart 等)都会被重新执行,导致整个图表视图重新计算和绘制。
  • 视图重构导致的重复计算
    • 由于 SwiftUI 的声明式特性,每次视图出现时都会重新计算其内部状态,进而触发大量重复的计算操作(比如对时间范围、轴值和样式的计算),所以返回时会明显卡顿,耗时达到 56 秒。

3. 优化思路和解决方案

针对上述问题,可以从以下几个方面考虑优化:

a. 数据和样式的预计算与缓存
  • 提前计算图表数据范围和样式
    • x 轴和 y 轴的计算、刻度值生成、以及各个数据点对应的样式等计算移到 viewModel 中,提前计算好并缓存。这样在视图构建时,只需直接使用缓存结果,避免重复计算。
  • 缓存 StrokeStyle 和颜色
    • 如果 chartLineWidthlineCaplineJoin 等参数不会变化,可以将生成的 StrokeStyle 定义为常量,避免在 ForEach 内部重复创建。
b. 减少不必要的视图重构
  • 局部视图拆分和懒加载

    • 将复杂且耗时的图表部分拆分成独立的子视图,利用 SwiftUI@StateObject@ObservedObject 缓存数据,防止因父视图重新构建而全部重绘。
    • 如果页面上图表较多,可以考虑使用 LazyVStack 替换 VStack,使得只有当视图真正需要显示时才进行计算和绘制。
  • 视图缓存策略

    • 当数据不频繁变化时,可以考虑在视图中利用 .drawingGroup() 或其他缓存手段,将渲染结果缓存成图像,在下一次展示时直接使用缓存图像而不是重新绘制所有的线条。
c. 关注 SwiftUI 的绘图和布局机制
  • 审查 Chart 的内部实现
    • SwiftUIChart 组件可能在处理大量数据点时没有做到最优。如果可能,考虑对数据进行适当的抽样或者合并,减少绘制的元素数量。
  • 分析 View 重构触发机制
    • 检查是否存在不必要的状态更新或绑定,导致整个 LineChartView 被频繁重构。如果能做到局部状态隔离,只让真正需要更新的部分重绘,也会大大降低耗时。

总结

  1. 问题定位

    • 耗时主要在 ForEach 循环中生成各个 LineMark 的过程中,特别是 .lineStyle 的计算,以及在视图重构时重复计算各项轴值和样式。
    • 导航返回时,因为整个视图重新构建,所有的图表闭包都会再次执行,造成明显卡顿。
  2. 优化方案

    • 预计算和缓存 :在 viewModel 中提前计算好各个图表需要的数据、刻度、样式等,避免在视图中重复计算。
    • 减少重复视图构建 :将耗时较大的图表部分拆分为独立的子视图,并使用懒加载、缓存视图或 .drawingGroup() 技术来减少重绘。
    • 调整数据量:如果数据点非常多,可以考虑做数据抽样或聚合,降低绘图时的计算量。
相关推荐
海尔辛4 小时前
Unity UI 性能优化--Sprite 篇
ui·unity·性能优化
凌佚7 小时前
rknn优化教程(一)
c++·目标检测·性能优化
橘子青衫8 小时前
Java并发编程利器:CyclicBarrier与CountDownLatch解析
java·后端·性能优化
DemonAvenger14 小时前
Go并发编程:内存同步与竞态处理
性能优化·架构·go
橘子青衫1 天前
Java多线程编程:深入探索线程同步与互斥的实战策略
java·后端·性能优化
低调的JVM1 天前
Async-profiler 内存采样机制解析:从原理到实现
java·c++·性能优化
全栈技术负责人1 天前
H5移动端性能优化策略(渲染优化+弱网优化+WebView优化)
性能优化
星辰离彬1 天前
Java Stream 高级实战:并行流、自定义收集器与性能优化
java·开发语言·后端·性能优化
七夜zippoe1 天前
JavaScript性能优化实战:从核心原理到工程实践的全流程解析
javascript·性能优化