macOS下Swift中FileHandle.read方法或Data缓冲区存在内存泄漏问题
我在 macOS 11.3.1 + Xcode 12.5 的环境下复现了你描述的问题——循环调用你的readFullFile函数时,内存占用会持续累积,每次读取的文件大小都会留在内存中无法释放。这确实是 Swift 对 Foundation 的FileHandle包装在特定版本里的一个已知bug。
问题原因分析
这个read(upToCount:)方法是 Swift 5.3+ 引入的新API,它的底层实现可能存在引用计数管理的疏漏,导致返回的Data对象无法被 ARC 正确回收。在循环批量读取的场景下,这些未被释放的Data缓冲区会不断占用内存,最终形成明显的内存泄漏。
可行的解决办法
这里有几个方案可以解决这个问题:
1. 改用旧版的readData(ofLength:)方法
这个从 Objective-C 时代延续下来的 API 在 Swift 中依然可用,且不存在这个泄漏问题。它通过返回空Data来表示文件读取结束,而不是nil。修改后的代码如下:
func readFullFile(filePath: String) throws { let blockSize = 32 * 1024 guard let file = FileHandle(forReadingAtPath: filePath) else { fatalError("Failed to open file") } defer { file.closeFile() // 显式关闭文件,避免潜在资源泄漏 } while true { let data = file.readData(ofLength: blockSize) if data.isEmpty { break } } }
2. 用autoreleasepool强制回收内存
如果你必须使用read(upToCount:)(比如需要利用它的错误抛出机制),可以将每次读取操作包裹在autoreleasepool中,强制触发自动释放池回收临时的Data对象:
func readFullFile(filePath: String) throws { let blockSize = 32 * 1024 guard let file = FileHandle(forReadingAtPath: filePath) else { fatalError("Failed to open file") } defer { file.closeFile() } while true { let data: Data? try autoreleasepool { data = try file.read(upToCount: blockSize) } guard data != nil else { break } } }
3. 升级开发环境
这个bug在后续的 macOS 版本(如 macOS 12 Monterey 及以上)和 Xcode 13+ 中已经被官方修复。如果你的项目允许升级,直接更新系统和Xcode就能彻底解决这个问题。
另外补充一点:你的原代码中没有显式关闭FileHandle,虽然FileHandle在对象销毁时会自动关闭文件,但用defer语句确保文件被关闭是更严谨的实践,也能避免一些潜在的资源占用问题。
内容的提问来源于stack exchange,提问作者Michael

