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

SwiftUI连分数递归视图性能问题排查与优化咨询

问题原因
  • 深层递归的视图嵌套:每一层连分数都会创建新的HStack、多个VStack和Text,当列表长度达到7-8时,视图树的深度和节点数会急剧膨胀。SwiftUI的布局系统需要递归计算每个子视图的尺寸与位置,深层嵌套会导致布局计算量暴增,直接阻塞UI线程。
  • 不必要的数组切片开销:每次递归都通过Array(continuedFraction[1...])创建新数组,虽然单步开销不大,但多层递归累加后会增加内存分配和数据拷贝的负担。
优化方案

方案1:迭代构建单视图(推荐)

放弃递归视图结构,通过字符串拼接结合富文本格式,把整个连分数渲染成一个独立的Text视图,彻底避免深层嵌套:

import SwiftUI

struct ContinuedFractionView: View {
    let continuedFraction: [Int]
    
    private var fractionContent: AttributedString {
        guard continuedFraction.count > 1 else {
            return AttributedString(String(continuedFraction[0]))
        }
        
        var result = AttributedString("\(continuedFraction[0]) + ")
        var denominatorSegments = continuedFraction[1...].map { String($0) }
        
        // 从后往前构建分母的嵌套结构
        var denominatorStr = denominatorSegments.removeLast()
        while let segment = denominatorSegments.popLast() {
            denominatorStr = "\(segment) + 1/\(denominatorStr)"
        }
        
        // 给分数部分添加上下标格式
        let fraction = AttributedString("1/\(denominatorStr)")
        if let slashRange = fraction.range(of: "/") {
            fraction[..<slashRange.lowerBound].font = .system(size: 12)
            fraction[..<slashRange.lowerBound].baselineOffset = 6
            fraction[slashRange.upperBound...].font = .system(size: 12)
            fraction[slashRange.upperBound...].baselineOffset = -6
        }
        
        result.append(fraction)
        return result
    }
    
    var body: some View {
        Text(fractionContent)
            .font(.title)
    }
}

struct ContinuedFractionView_Previews: PreviewProvider {
    static var previews: some View {
        ContinuedFractionView(continuedFraction: [1,2,3,4,5,6,7,8,9])
    }
}

方案2:Canvas自定义绘制

如果需要更精确的排版控制(比如分数线对齐),可以用SwiftUI的Canvas直接绘制整个连分数结构,完全规避视图嵌套的性能问题:

import SwiftUI

struct ContinuedFractionView: View {
    let continuedFraction: [Int]
    let fontSize: CGFloat = 20
    let lineWidth: CGFloat = 1
    
    var body: some View {
        Canvas { context, size in
            guard !continuedFraction.isEmpty else { return }
            
            var currentX: CGFloat = 10
            let baselineY = size.height / 2
            
            // 绘制第一个数字
            drawText(String(continuedFraction[0]), at: CGPoint(x: currentX, y: baselineY), context: context)
            currentX += textWidth(String(continuedFraction[0])) + 5
            
            // 迭代绘制后续的"+ 1/..."结构
            for num in continuedFraction[1...] {
                // 绘制"+"
                drawText("+", at: CGPoint(x: currentX, y: baselineY), context: context)
                currentX += textWidth("+") + 5
                
                // 绘制分子"1"(上标)
                let numeratorY = baselineY - fontSize * 0.6
                drawText("1", at: CGPoint(x: currentX, y: numeratorY), context: context)
                currentX += textWidth("1") + 2
                
                // 绘制分数线
                let lineStartY = baselineY - fontSize * 0.3
                let lineEndY = baselineY + fontSize * 0.3
                context.stroke(
                    Path { path in
                        path.move(to: CGPoint(x: currentX - textWidth("1") - 2, y: lineStartY))
                        path.addLine(to: CGPoint(x: currentX - textWidth("1") - 2, y: lineEndY))
                    },
                    with: .black,
                    lineWidth: lineWidth
                )
                
                // 绘制当前分母数字
                drawText(String(num), at: CGPoint(x: currentX, y: baselineY), context: context)
                currentX += textWidth(String(num)) + 5
            }
        }
        .frame(height: fontSize * 2)
    }
    
    private func drawText(_ text: String, at point: CGPoint, context: GraphicsContext) {
        let attrString = AttributedString(text)
        context.draw(attrString, at: point, anchor: .leading)
    }
    
    private func textWidth(_ text: String) -> CGFloat {
        AttributedString(text).size().width
    }
}

struct ContinuedFractionView_Previews: PreviewProvider {
    static var previews: some View {
        ContinuedFractionView(continuedFraction: [1,2,3,4,5,6,7,8,9])
    }
}

方案3:递归结构优化(效果有限)

如果一定要保留递归逻辑,可以通过精简视图容器、缓存切片结果来小幅提升性能,但无法从根本解决深层嵌套的问题:

  • 用@ViewBuilder合并冗余的VStack
  • 提前缓存数组切片,避免重复创建
  • 给递归视图添加固定尺寸约束,减少布局重计算

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 08:20:22