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

SwiftUI List简单场景下内存泄漏问题排查求助

SwiftUI List 单元格内存管理探究问题

问题简述

环境:macOS 12.6.1、Xcode 14.2、iOS 16.2
为探究SwiftUI List在单元格显示/隐藏时的分配与释放逻辑,创建了演示项目,包含以下组件:

  • Payload:数据模型,包含id: Int和image: Image
  • PayloadViewModel:管理[Payload],实例化时生成300个Payload对象(id从0到299),每个对象的image是100×100的彩色随机噪点图
  • ContentView:通过PayloadRow在List中展示viewModel.payloads数据

预期行为

  • 应用启动后,PayloadViewModel实例化并加载所有图片到数组,内存占用应约为52 MB
  • 滚动List时内存不应显著增长,因为只需保留300个Payload对象,加上5-6个可见单元格的额外实例,且List仅复用可见单元格

实际行为

  • 应用刚启动内存约20 MB,PayloadViewModel生成完所有Payload后内存为26.3 MB,未达到预期的52 MB
  • 滚动列表时内存持续增长,滚动到底部时达到52 MB,且非可见单元格未被释放

Instruments分析

使用Allocations工具分析:

  1. 启动应用等待30秒,待所有Payload生成后(不滚动),筛选最大尺寸的持久化对象,发现VM: CG raster data占用内存最大
  2. 滚动到最后一个单元格时,VM: CoreAnimation占用内存与CG raster data相当

当前猜想

应用刚启动时内存较低,可能是图片以压缩格式存储在内存中,显示时才解压为实际尺寸;但滚动时内存持续增长的原因尚不明确。

复现步骤

方式一:创建新项目,替换ContentView为下方代码
方式二:执行以下命令:

git clone git@github.com:DmytroKoval8/SwiftUIListExperiment.git
cd SwiftUIListExperiment
git checkout images-for-viewmodel-generated-randomly

也可切换到从资源库加载图片的分支:

git checkout images-for-viewmodel-loaded-from-assets

演示代码

import SwiftUI
import Combine

class PayloadViewModel: ObservableObject {
    @Published var payloads: [Payload] = []

    init() {
        generatePayloads()
    }

    func generatePayloads() {
        DispatchQueue.global(qos: .userInitiated).async { [weak self] in
            guard let self = self else { return }
            for id in 0..<300 {
                let uiImage = self.generateRandomImage(width: 100, height: 100)
                let image = Image(uiImage: uiImage)
                let payload = Payload(id: id, image: image)
                DispatchQueue.main.async {
                    self.payloads.append(payload)
                }
                print("Image \(id) generated")
            }
        }
    }

    func generateRandomImage(width: Int, height: Int) -> UIImage {
        let size = CGSize(width: width, height: height)
        UIGraphicsBeginImageContext(size)
        let context = UIGraphicsGetCurrentContext()!

        for y in 0..<height {
            for x in 0..<width {
                let color = UIColor(
                    red: CGFloat(arc4random_uniform(256)) / 255.0,
                    green: CGFloat(arc4random_uniform(256)) / 255.0,
                    blue: CGFloat(arc4random_uniform(256)) / 255.0,
                    alpha: 1.0
                )
                context.setFillColor(color.cgColor)
                context.fill(CGRect(x: x, y: y, width: 1, height: 1))
            }
        }

        let image = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return image
    }
}

struct Payload: Identifiable {
    let id: Int
    let image: Image
}

struct ContentView: View {
    @StateObject var viewModel = PayloadViewModel()

    var body: some View {
        List(viewModel.payloads) { payload in
            PayloadRow(payload: payload)
                .onAppear {
                    print("Appear: \(payload.id)")
                }
                .onDisappear {
                    print("Disappear: \(payload.id)")
                }
        }
    }
}

struct PayloadRow: View {
    let payload: Payload

    var body: some View {
        HStack {
            payload.image
                .resizable()
                .frame(width: 100, height: 100)
            Text("Payload \(payload.id)")
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 03:48:13