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) } } } }
原因分析
- List的视图重用机制:
List底层基于原生UITableView(iOS)实现,采用视图重用池管理行视图。删除元素时,对应的行视图不会立即销毁,而是被存入重用池等待复用。 - @State的强引用残留:
StreamerView中使用@State var streamer: Streamer持有对象,@State会对存储的实例保持强引用。即使modelManager的数组移除了Streamer,只要重用池里的StreamerView未被销毁,@State的强引用就会阻止对象被释放。 - 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
相关产品推荐
相关产品推荐

