Swift类未及时释放:SwiftUI macOS应用内存管理问题求助
问题分析与解决方案
问题根源
- 强引用未及时解除:原代码中
fileSelection是Set<FileItem>,直接持有选中实例的强引用;且fileRemove方法在循环内部就清空选中集合,逻辑错误,导致即使从myFiles移除实例,fileSelection仍持有引用,实例无法销毁。 - 数组类型冗余:
myFiles定义为[FileItem?],但实际使用时会过滤nil,可选包装增加了内存管理复杂度。 - 相等性逻辑隐患:
FileItem用fileName判断相等,不同实例若文件名相同会被误判为相等,可能导致索引查找错误,同时影响集合的引用管理。 - macOS自动释放池机制:主线程自动释放池默认在run loop周期结束时清理对象,后台切换才触发run loop,这是之前只有切换后台才释放的直接原因,但核心还是引用未彻底解除。
修复方案
1. 修正FileItem的哈希与相等性逻辑
改用Identifiable自带的id属性判断相等和哈希,避免同名文件导致的逻辑错误:
class FileItem: Hashable, Identifiable { let id = UUID() // 设为不可变,保证唯一性 let fileName: String init(fileName: String) { self.fileName = fileName print("Class Initialized: \(fileName)") } deinit { print("Class DeInitialized: \(fileName)") } static func == (lhs: FileItem, rhs: FileItem) -> Bool { return lhs.id == rhs.id // 用唯一ID判断相等 } func hash(into hasher: inout Hasher) { hasher.combine(id) // 用ID生成哈希值 } }
2. 重构ViewModel,消除强引用隐患
- 将
myFiles改为非可选数组,简化内存管理 - 用
Set<FileItem.ID>跟踪选中项,避免直接持有实例强引用 - 修正
fileRemove方法逻辑,确保所有引用彻底解除 - 加入手动自动释放池,强制触发实例销毁:
extension TestView { @MainActor class ViewModel: ObservableObject { @Published var myFiles: [FileItem] = [] @Published var fileSelection: Set<FileItem.ID> = [] func fileRemove() { // 移除所有选中ID对应的文件 myFiles.removeAll { fileSelection.contains($0.id) } fileSelection = [] // 手动触发自动释放池,立即清理无引用对象 autoreleasepool { } } func fileRemoveAll() { myFiles = [] fileSelection = [] autoreleasepool { } } // 测试用添加文件方法 func addTestFile(named name: String) { myFiles.append(FileItem(fileName: name)) } } }
3. 调整TestView适配修改后的ViewModel
去掉不必要的compactMap,直接遍历非可选数组:
struct TestView: View { @StateObject var viewModel: ViewModel var body: some View { VStack { Button { viewModel.addTestFile(named: "File \(Date().timeIntervalSince1970)") } label: { Text("Add Test File") } Button { viewModel.fileRemove() } label: { Text("Remove Selected") } Button { viewModel.fileRemoveAll() } label: { Text("Remove All") } List(selection: $viewModel.fileSelection) { ForEach(viewModel.myFiles) { file in Text(file.fileName) } .onMove { indices, destination in viewModel.myFiles.move(fromOffsets: indices, toOffset: destination) } } .frame(height: 300) } .padding() } } struct TestView_Previews: PreviewProvider { static var previews: some View { TestView(viewModel: TestView.ViewModel()) } }
修复效果
修改后,删除选中文件或清空列表时,对应的FileItem实例会立即触发deinit方法,控制台会打印释放日志,无需等待应用进入后台。
内容的提问来源于stack exchange,提问作者kittonian
相关产品推荐
相关产品推荐

