如何测试NSString与autoreleasepool泄漏?



试图修复300MB内存泄漏,并在找到泄漏原因后;

(从c++线程(没有@autoreleasepool块包装器)调用NSStringstringFromUTF8String:)

我编辑了代码,强制引用计数(而不是自动释放),如下所示:
public func withNSString(
_ chars: UnsafePointer<Int8>,
_ callback: (NSString) -> Void
) {
let result: NSString = NSString(utf8String: chars)!;
callback(result);
}

作为个人策略,使用单元测试,如:

import Foundation
import XCTest
@testable import MyApp
class AppTest: XCTestCase {
func testWithNSString_hasNoMemoryLeak() {
weak var weakRef: NSString? = nil
autoreleasepool {
let chars = ("some data" as NSString).utf8String!;
withNSString(chars, { strongRef in
weakRef = strongRef;
XCTAssertNotNil(weakRef);
})
// Checks if reference-counting is used.
XCTAssertNil(weakRef); // Fails, so no reference-counting. 
}
// Checks if autoreleased.
XCTAssertNil(weakRef); // Fails, OMG! what is this?
}
}

但是现在,甚至连自动释放似乎都不起作用了(-_- )
为什么最后一次XCTAssertNil调用失败?
(换句话说,我如何修复内存泄漏?)

问题是您使用了一个非常短的字符串。它被内联到堆栈上,所以直到整个堆栈帧超出作用域它才被释放。如果你让字符串长一点(长2个字符),这将按照你期望的方式运行。当然,这是一个实现细节,可能会因编译器的不同版本、操作系统的不同版本、优化设置的不同或体系结构的不同而改变。

请记住,使用任何类型的静态字符串测试这类东西都可能很棘手,因为静态字符串被放置在二进制文件中。因此,如果编译器注意到你间接地创建了一个指向静态字符串的指针,那么它可能会优化这个间接,而不释放它。

在这些情况下都不存在内存泄漏。内存泄漏更有可能发生在withNSString调用代码中。我主要怀疑您没有正确处理作为chars传递的字节。我们需要更多地了解你为什么认为有泄密来评估。(Foundation也有一些小的泄漏,而Instruments在泄漏上有误报,所以如果你在追逐小于50字节的分配,并且不是在每个操作中都重复出现,你可能在追逐幽灵。)


注意,这有点危险:

let chars = ("some data" as NSString).utf8String!
withNSString(chars, { strongRef in

utf8String内部指针不保证比NSString存在的时间长,Swift可以在对象的最后一次引用之后销毁它们(可能是在它们超出作用域之前)。如文档所述:

这个C字符串是一个指向string对象内部结构的指针,它的生存期可能比string对象短,当然也不会比string对象长。因此,如果需要将C字符串存储在使用该属性的内存上下文之外,则应该复制该C字符串。

在这种情况下,对象是一个常量字符串,它是二进制的,不能被销毁。但在更普遍的情况下,这是导致崩溃的典型原因。我强烈建议不要使用NSString接口,而是使用String。它提供了utf8CString,它返回一个正确的ContinguousArray,这是更安全的。

let chars = "some data".utf8CString
chars.withUnsafeBufferPointer { buffer in
withNSString(buffer.baseAddress!, { strongRef in
weakRef = strongRef;
XCTAssertNotNil(weakRef);
})
}

withUnsafeBufferPointer确保chars在块完成之前不会被破坏。

如果需要,您还可以确保字符串的生命周期(这对于修复您不想以更安全的方式重写的旧代码非常有用):

let string = "some data"
withExtendedLifetime(string) {
let chars = string.utf8CString
chars.withUnsafeBufferPointer { buffer in
withNSString(buffer.baseAddress!, { strongRef in
weakRef = strongRef;
XCTAssertNotNil(weakRef);
})
}
}

最新更新