调用ByteCountFormatter的string方法内存暴涨,添加autoreleasepool后恢复正常求解析
ByteCountFormatter内存增长与autoreleasepool的作用解析
问题背景
开发的应用存在内存增长异常问题,经排查定位到根源是ByteCountFormatter的使用方式。为验证问题编写了如下单元测试代码:
初始测试代码(内存快速增长)
import XCTest final class DiskUtilitiesTests : XCTestCase { let formatter = ByteCountFormatter() func testMemoryLeaks() { self.formatter.allowedUnits = .useBytes self.formatter.includesUnit = true for i in 0 ..< UInt64.max { let string = self.formatter.string(fromByteCount: Int64(i)) } } }
运行这段测试时内存增长极快,即便循环结束,字符串占用的内存也没有立即释放。
添加autoreleasepool后的代码(内存稳定)
import XCTest final class DiskUtilitiesTests : XCTestCase { let formatter = ByteCountFormatter() func testMemoryLeaks() { self.formatter.allowedUnits = .useBytes self.formatter.includesUnit = true for i in 0 ..< UInt64.max { autoreleasepool { let string = self.formatter.string(fromByteCount: Int64(i)) } } } }
此时内存消耗在Xcode和活动监视器中均保持稳定。
原因解析
- Objective-C自动释放池机制影响:
ByteCountFormatter是基于Objective-C实现的Foundation框架类,它返回的字符串对象默认会被加入当前线程的自动释放池,不会立即释放内存。 - 默认自动释放池的清空时机:iOS/macOS的默认RunLoop中,自动释放池仅在每次RunLoop循环结束时才会清空,释放池内对象。而你的循环是连续同步执行的逻辑,不会触发RunLoop的循环结束,导致自动释放池里的字符串对象不断累积,内存持续暴涨。
- 手动autoreleasepool的作用:在循环内部添加
autoreleasepool代码块后,每次循环迭代都会创建一个独立的小型自动释放池,代码块执行完毕后立即清空该池,释放其中生成的字符串对象,从根源避免了内存累积,让内存消耗保持稳定。 - 并非真正的内存泄漏:初始代码中的内存未释放不是真泄漏,只是对象被延迟释放。如果循环结束后等待RunLoop执行一次(比如添加延迟或回到主循环),内存也会回落,但在长时间循环场景下,这种延迟释放会引发内存暴涨,必须手动干预释放时机。
内容的提问来源于stack exchange,提问作者inexcitus
相关产品推荐
相关产品推荐

