我碰到了最奇怪的事,也许有人有解释。步骤:
hierarchy
H 111弱引用仍然存在。H 212G 213/code>
如果您跳过步骤3和步骤4,弱引用将如预期的那样变为零。
测试代码:
TestView检查deinit
class TestView: UIView {
deinit {
print("deinit")
}
}单元测试
class RetainTests: XCTestCase {
func testRetainFails() {
let holderView = UIView()
var element: TestView? = TestView()
holderView.addSubview(element!)
element?.removeFromSuperview()
weak var weakElement = element
XCTAssertNotNil(weakElement)
// after next line `weakElement` should be nil
element = nil
// console will print "deinit"
XCTAssertNil(weakElement) // fails!
}
func testRetainPasses() {
var element: TestView? = TestView()
weak var weakElement = element
XCTAssertNotNil(weakElement)
// after next line `weakElement` should be nil
element = nil
// console will print "deinit"
XCTAssertNil(weakElement)
}
}这两个测试都会在控制台上打印出deinit,但在失败测试的情况下,如果element在视图层次结构中存在任何时间,则weakElement仍然保留引用,尽管element已成功地被解除分配。这怎么可能呢?
(编辑:这是一个应用程序,而不是游乐场)
发布于 2022-03-14 02:41:19
当您将TestView添加并移除到superview时,会有一个自动发布附加到它。如果确保耗尽自动释放池,则此测试的行为与您预期的一样:
autoreleasepool {
holderView.addSubview(element!)
element?.removeFromSuperview()
}也就是说,你不能依赖这种行为。一个物体何时会被摧毁,这是没有承诺的。你只知道这是在你发布你最后一次强烈的参考之后。对象上可能有附加的保留或自动释放。也没有承诺deinit将永远被调用,所以一定要确保不将任何强制性的逻辑放在那里。它只应该执行内存清理。(出于稍微不同的原因,Mac和iOS都不会在程序终止期间调用deinit。)
在失败的测试用例中,deinit是在断言失败后打印的。您可以通过在失败的断言和print上放置断点来演示这一点。这是因为自动释放池在测试用例的末尾被耗尽。
底层的教训是,在传递给ObjC代码的对象上放置平衡的very /自动释放调用是非常常见的( UIKit是严重的ObjC)。通常不应该期望在当前事件循环结束之前销毁UIKit对象。不要依赖这个(这不是承诺),但这往往是真的。
https://stackoverflow.com/questions/71446860
复制相似问题