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

SwiftUI List删除元素后存在内存泄漏问题求助

SwiftUI List删除元素后对象未释放的问题分析与解决

问题场景

开发SwiftUI应用时,使用List展示Streamer对象列表,删除元素后Streamer的deinit方法始终不触发,对象持续占用内存;但将List替换为ScrollView后,内存能正常释放,deinit也会按预期执行。

核心代码片段

Streamer与Model定义

@Observable
class Streamer: Identifiable {
    let id = UUID()
    let name: String
    var state: streamerState
    var title: String
    
    init(name: String, state: streamerState, title: String) {
        self.name = name
        self.state = state
        self.title = title
    }
    
    deinit {
        print("\(name) has deinitialized")
    }
}

@Observable
final class modelManager {
    var allStreamers: [Streamer] = []
    
    func addStreamer(_ newStreamer: String) {
        allStreamers.append(
            Streamer(
                name: newStreamer,
                state: streamerState.allCases.randomElement()!,
                title: "my title is \(Int.random(in: 1...100))"
            )
        )
    }
    
    func removeStreamer(named nameToRemove: String) {
        if let index = allStreamers.firstIndex(where: { $0.name == nameToRemove }) {
            allStreamers.remove(at: index)
        }
    }
}

UI实现代码

struct MainUI: View {
    @State private var streamerName = ""
    @Environment(modelManager.self) var model
    
    var body: some View {
        VStack {
            UserField(streamerName: streamerName)
            
            List {
                ForEach(model.allStreamers, id: \.id) { streamer in
                    StreamerView(streamer: streamer)
                    Button("Delete") {
                        model.removeStreamer(named: streamer.name)
                    }
                }
            }
        }
    }
}

struct StreamerView: View {
    @State var streamer: Streamer
    @Environment(modelManager.self) var model
    
    var body: some View {
        VStack {
            HStack {
                Circle()
                    .fill(streamer.state == .online ? Color.green : Color.red)
                    .frame(width: 12, height: 12)
                
                Text(streamer.name)
                    .lineLimit(1)
                
                Spacer()
                
                Text(streamer.title)
                    .fontWeight(.light)
                    .lineLimit(1)
                
                Spacer()
                
                Text(streamer.state.rawValue)
                    .foregroundStyle(streamer.state == .online ? Color.green : Color.red)
                    .animation(.bouncy(duration: 1.5), value: streamer.state)
            }
            
            Button("Delete") {
                model.removeStreamer(named: streamer.name)
            }
        }
    }
}

原因分析

  1. List的视图重用机制:List底层基于原生UITableView(iOS)实现,采用视图重用池管理行视图。删除元素时,对应的行视图不会立即销毁,而是被存入重用池等待复用。
  2. @State的强引用残留:StreamerView中使用@State var streamer: Streamer持有对象,@State会对存储的实例保持强引用。即使modelManager的数组移除了Streamer,只要重用池里的StreamerView未被销毁,@State的强引用就会阻止对象被释放。
  3. ScrollView的差异:ScrollView没有视图重用逻辑,删除元素后对应的StreamerView会立即销毁,@State的强引用随之释放,对象就能正常触发deinit。

解决方案

将StreamerView中的@State var streamer: Streamer改为普通的let声明——由于Streamer是@Observable类,直接传递引用即可,无需@State额外持有:

struct StreamerView: View {
    let streamer: Streamer // 替换@State为let
    @Environment(modelManager.self) var model
    
    var body: some View {
        // 原有UI代码保持不变
    }
}

原理说明

  • @Observable类是引用类型,用let声明可直接获取对象引用,UI会自动响应其属性变化。
  • 移除@State后,StreamerView不再额外持有Streamer的强引用,当List的行视图被销毁或复用时,不会保留对已删除对象的引用,对象即可被正常释放并触发deinit。

若需在StreamerView中修改streamer的属性(如state),直接通过streamer.state = ...修改即可,@Observable会自动通知UI更新。

内容的提问来源于stack exchange,提问作者HáMzà VyNøs

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 02:21:16