如何使用 autoreleasepool 泄漏测试 NSString?
How to test NSString with autoreleasepool leak?
正在尝试修复 300MB
内存泄漏,并在找到泄漏原因后;
(Which was calls to NSString
's stringFromUTF8String:
, from C++ thread (without @autoreleasepool
-block wrapper))
我编辑了代码,以强制执行引用计数(而不是自动释放),如下所示:
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 个字符),这将按您预期的方式运行。当然,这是一个实现细节,可能会因不同版本的编译器、不同版本的 OS、不同的优化设置或不同的架构而发生变化。
请记住,使用任何类型的静态字符串测试此类内容可能很棘手,因为静态字符串已放入二进制文件中。因此,如果编译器注意到您间接创建了一个指向静态字符串的指针,那么它可能会优化间接寻址而不释放它。
但在这些情况中 none 存在内存泄漏。您的内存泄漏更有可能发生在 withNSString
的 调用代码 中。我主要怀疑您没有正确处理作为 chars
传递的字节。我们需要更多地了解您认为存在泄漏的原因才能对其进行评估。 (Foundation 也有一些小泄漏,而 Instruments 对泄漏有误报,所以如果你正在追逐一个小于 50 字节的分配并且不会在每个操作中重现,你可能正在追逐幽灵。)
注意这有点危险:
let chars = ("some data" as NSString).utf8String!
withNSString(chars, { strongRef in
utf8String
内部指针未承诺比 NSString 更长寿,并且 Swift 可以在对象最后一次引用后(可能在它们超出范围之前)自由销毁对象。正如文档所述:
This C string is a pointer to a structure inside the string object, which may have a lifetime shorter than the string object and will certainly not have a longer lifetime. Therefore, you should copy the C string if it needs to be stored outside of the memory context in which you use this property.
在这种情况下对象是一个常量字符串,它在二进制文件中并且不能被销毁。但在更一般的情况下,这是崩溃的典型原因。我强烈建议远离 NSString 接口并使用 String。它提供 utf8CString
,其中 returns 一个适当的 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);
})
}
}
正在尝试修复 300MB
内存泄漏,并在找到泄漏原因后;
(Which was calls to
NSString
'sstringFromUTF8String:
, from C++ thread (without@autoreleasepool
-block wrapper))
我编辑了代码,以强制执行引用计数(而不是自动释放),如下所示:
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 个字符),这将按您预期的方式运行。当然,这是一个实现细节,可能会因不同版本的编译器、不同版本的 OS、不同的优化设置或不同的架构而发生变化。
请记住,使用任何类型的静态字符串测试此类内容可能很棘手,因为静态字符串已放入二进制文件中。因此,如果编译器注意到您间接创建了一个指向静态字符串的指针,那么它可能会优化间接寻址而不释放它。
但在这些情况中 none 存在内存泄漏。您的内存泄漏更有可能发生在 withNSString
的 调用代码 中。我主要怀疑您没有正确处理作为 chars
传递的字节。我们需要更多地了解您认为存在泄漏的原因才能对其进行评估。 (Foundation 也有一些小泄漏,而 Instruments 对泄漏有误报,所以如果你正在追逐一个小于 50 字节的分配并且不会在每个操作中重现,你可能正在追逐幽灵。)
注意这有点危险:
let chars = ("some data" as NSString).utf8String!
withNSString(chars, { strongRef in
utf8String
内部指针未承诺比 NSString 更长寿,并且 Swift 可以在对象最后一次引用后(可能在它们超出范围之前)自由销毁对象。正如文档所述:
This C string is a pointer to a structure inside the string object, which may have a lifetime shorter than the string object and will certainly not have a longer lifetime. Therefore, you should copy the C string if it needs to be stored outside of the memory context in which you use this property.
在这种情况下对象是一个常量字符串,它在二进制文件中并且不能被销毁。但在更一般的情况下,这是崩溃的典型原因。我强烈建议远离 NSString 接口并使用 String。它提供 utf8CString
,其中 returns 一个适当的 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);
})
}
}