正在调用Swift deinit,但对象仍然没有解除分配

NJG*_*GUY 6 xcode instruments swift

在swift中,我得到了deinit函数来打印出一条线,表示该对象已被初始化,但该对象仍然被报告为在Instruments,allocations工具中生效.我认为这甚至不可能.有没有办法找出它没有被释放的原因?或者有没有办法找出哪些儿童对象可以举起它?

jha*_*ott 12

更新:对于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
Run Code Online (Sandbox Code Playgroud)

在分配object给新实例(第6行)之后,您会认为对原始对象的弱引用将为零,但它(尚未).该对象被删除但保持分配(在内存中)直到所有弱引用都消失.在某一点上A,弱引用仍然存在,当你试图评估Swift检查的弱引用并且注意到它引用的对象被删除时,它只在下一行,所以它将弱引用归零,然后将它传递给print函数打印.这种机制需要对象的空壳保持分配,直到所有弱引用都消失.它被称为一个外壳,因为它的所有属性都被归零并释放,deinit因此它不会保留其他任何东西(对象的内存量非常小,足以存储其内部头和成员).

为什么以及如何?

每个对象都有一个内部弱引用计数,而不是需要归零的引用列表.这样做更快,资源更少,deinit因为以线程安全的方式清零弱引用列表需要相当长的原子/同步操作.

当强引用计数达到零时,deinit调用并且对象进入deallocating状态.运行时保持分配的内存,因为每当访问弱引用时它需要检查对象的状态.一旦所有弱引用都被访问并归零(弱引用计数为零),内存将被释放并且释放完成.

看一下swift_weakLoadStrongswift源代码的实现 - 这是在访问弱引用并强化(例如分配给强引用或传递给函数等)时插入的代码.我在下面缩写.查看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;
Run Code Online (Sandbox Code Playgroud)

您可以看到对象外壳仍然存在于内存中,并且加载弱引用的机制(即,当您在代码中访问它时)检查它是否正在解除分配,如果是,则将弱引用归零,调用swift_unownedRelease减少弱引用引用计数并在计数达到零时释放对象,并返回nullptr(nil).

Swift 4更新

从Swift 4开始,弱引用具有改进的实现,这意味着对象外壳不再需要挂起.相反,使用了一个小的边桌,弱的参考实际上指向了那个.边表包含指向真实对象的指针,Swift在访问弱引用时知道遵循此指针.有关更详细的解释,请阅读Mike Ash撰写的这篇精彩博文.


Nun*_*ves 0

确保您与其他对象没有任何牢固的关系。想象一下以下情况:

class A {
   var b: B
}

class B {
   var a: A
}

a.b = xxx
b.a = yyy
Run Code Online (Sandbox Code Playgroud)

如果 A 持有对 B 的强引用,而 B 持有对 A 的强引用,则在它们之间创建强引用循环,并且设置
a = nil 不会调用 deinit,因为它持有对 b 的强引用。您可以将 ab 设置为 nil,或者使用弱引用(weak 关键字)来解决此问题。

请在此处查看苹果文档以获取更多详细信息

  • 线程启动器说 deinit 被调用。如果 deinit 被调用,没有人可以持有强引用。 (2认同)