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

Swift类未及时释放:SwiftUI macOS应用内存管理问题求助

问题分析与解决方案

问题根源

  1. 强引用未及时解除:原代码中fileSelection是Set<FileItem>,直接持有选中实例的强引用;且fileRemove方法在循环内部就清空选中集合,逻辑错误,导致即使从myFiles移除实例,fileSelection仍持有引用,实例无法销毁。
  2. 数组类型冗余:myFiles定义为[FileItem?],但实际使用时会过滤nil,可选包装增加了内存管理复杂度。
  3. 相等性逻辑隐患:FileItem用fileName判断相等,不同实例若文件名相同会被误判为相等,可能导致索引查找错误,同时影响集合的引用管理。
  4. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 08:04:56