【发布时间】:2015-05-15 04:31:47
【问题描述】:
在 swift 中,我让 deinit 函数打印出一行,说明对象已被取消初始化,但该对象仍在 Instruments、分配工具中报告为活动的。我什至不认为这是可能的。有没有办法找出为什么它没有被释放?或者有没有办法找出哪些子对象可能会阻止它?
【问题讨论】:
-
你能解决吗?我有同样的问题
标签: xcode swift instruments
在 swift 中,我让 deinit 函数打印出一行,说明对象已被取消初始化,但该对象仍在 Instruments、分配工具中报告为活动的。我什至不认为这是可能的。有没有办法找出为什么它没有被释放?或者有没有办法找出哪些子对象可能会阻止它?
【问题讨论】:
标签: xcode swift instruments
确保您与其他对象没有任何牢固的关系。想象一下:
class A {
var b: B
}
class B {
var a: A
}
a.b = xxx
b.a = yyy
如果 A 持有对 B 的强引用,而 B 持有对 A 的强引用,则在它们之间创建一个强引用循环,并设置
a = 无
不会调用 deinit ,因为它拥有对 b 的强引用。您要么将 a.b 设置为 nil,要么使用弱引用(weak 关键字)来解决这个问题。
查看苹果文档here了解更多详情
【讨论】:
我怀疑您正在查看不同的对象。为什么您认为名为@987654321@ 的对象与您在 Instruments 中看到的对象相同?实例数量超出您的想象(或只是查看错误的实例)是导致此类混淆的最常见原因之一。
您的deinit 只是打印语句,还是您在做其他事情?特别是,您是否正在做任何可能会意外再次留住您的事情? (我不记得这是否是明确定义的行为。)
【讨论】:
更新:对于 Swift 4,请参阅末尾的附加说明。
警告:这个答案详细介绍了 Swift 运行时的实现方式。此处的信息不会影响您在 Swift 中编写代码的方式,除非是在一些高级场景中。主要的一点是,从你作为程序员的角度来看,一旦调用了
deinit,对象对你来说就死了,你不能再使用它了。
内存未被释放的原因是 Swift 中的对象在被取消初始化时不一定会立即被释放(释放)。对对象的弱引用将导致对象的“外壳”保持分配状态(在内存中 - 你仍然不能使用它!),直到所有弱引用都被清零。
Swift 中的弱引用在对象被取消初始化时不会立即归零,但在下次访问它们时它们会归零。也就是说,Swift 会懒惰地将弱引用归零。这是一个例子:
public class MyClass {
}
var object = MyClass()
weak var weakObject = object
print (weakObject) // Points at a MyClass instance
object = MyClass()
// A: weakObject is not yet nil
print(weakObject) // prints 'nil'
// B: now weakObject is nil
在将object 分配给一个新实例之后(第6 行),您会认为对原始对象的弱引用将为零,但它不是(还)。该对象已deinited,但仍保持已分配(在内存中),直到所有弱引用都消失为止。在A 点,弱引用仍然存在,它仅在您尝试评估弱引用时出现在下一行,Swift 检查并注意到它引用的对象已取消初始化,因此它将弱引用归零,然后将其传递给要打印的print 函数。这种机制需要对象的空壳保持分配状态,直到所有弱引用都消失为止。它被称为外壳,因为它的所有属性都已归零并在deinit 中释放,因此它不会保留其他任何东西(对象的内存量非常小,仅足以存储其内部标题和成员) .
为什么以及如何?
每个对象都有一个内部弱引用计数,而不是需要归零的引用列表。这比deinit 更快且占用更少的资源,因为以线程安全的方式清零弱引用列表需要相当长的原子/同步操作。
当强引用计数为零时,调用deinit,对象进入deallocating状态。运行时保持分配的内存,因为它需要在访问弱引用时检查对象的状态。一旦所有弱引用都被访问并归零(弱引用计数为零),内存将被释放并完成释放。
从 swift 源代码中查看swift_weakLoadStrong 的实现 - 这是在访问弱引用并使其成为强引用时插入的代码(例如,分配给强引用或传递给函数等等。)。我在下面缩写了它。查看original code on github 以了解加载弱引用的全部复杂性:
if (object == nullptr) {
__atomic_store_n(&ref->Value, (uintptr_t)nullptr, __ATOMIC_RELAXED);
return nullptr;
}
if (object->refCount.isDeallocating()) {
__atomic_store_n(&ref->Value, (uintptr_t)nullptr, __ATOMIC_RELAXED);
SWIFT_RT_ENTRY_CALL(swift_unownedRelease)(object);
return nullptr;
}
auto result = swift_tryRetain(object);
__atomic_store_n(&ref->Value, ptr, __ATOMIC_RELAXED);
return result;
您可以看到对象外壳仍然存在于内存中,加载弱引用的机制(即当您在代码中访问它时)检查它是否正在释放,如果是,它将弱引用归零,调用@ 987654336@ 减少弱引用计数并在计数为零时释放对象,并返回nullptr (nil)。
Swift 4 更新
从 Swift 4 开始,弱引用有了改进的实现,这意味着对象外壳不再需要闲逛。取而代之的是一个小的边表,而弱引用实际上指向了它。边表包含一个指向真实对象的指针,Swift 知道在访问弱引用时要遵循这个指针。如需更详细的说明,请阅读 Mike Ash 的 this great blog post。
【讨论】:
Deinitializers are called automatically, just before instance deallocation takes placedeveloper.apple.com/library/content/documentation/Swift/…
deinit 的对象是不复存在;它不会“保持分配状态”。当然,对这个对象的弱引用需要一些记录,这将花费一些有限的时间,但它会在你有机会通过悬空指针引用对象之前发生,这才是最重要的。