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

时间:2015-03-12 20:27:01

标签: xcode swift instruments

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

3 个答案:

答案 0 :(得分:11)

  

更新:对于Swift 4,请参阅最后的附加说明。

     

警告:这个答案详细介绍了Swift运行时的实现方式。除了在某些高级方案中,此处的信息不会影响您在Swift中编写代码的方式。重点是,从你作为程序员的角度来看,一旦调用了deinit,对象就会死了,你就不能再使用了它。

没有释放内存的原因是Swift中的对象在被取消时不一定会被释放(释放)。对对象的弱引用将导致' husk'要保持分配的对象(在内存中 - 你仍然无法使用它!),直到所有弱引用都归零为止。

当对象被删除时,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;

你可以看到对象外壳仍然存在于内存中,并且加载弱引用的机制(即当你在代码中访问它时)检查它是否正在解除分配,如果是,则将弱引用置为零,调用{ {1}}递减弱引用计数并在计数达到零时释放对象,并返回swift_unownedReleasenullptr)。

Swift 4更新

自Swift 4以来,弱引用具有改进的实现,这意味着对象外壳不再需要闲置。相反,使用了一个小的边桌,弱的参考实际上指向了那个。边表包含指向真实对象的指针,Swift知道在访问弱引用时遵循此指针。有关更详细的说明,请阅读Mike Ash的this great blog post

答案 1 :(得分:2)

我怀疑你是在看不同的物体。为什么您认为调用deinit的对象与您在Instruments中看到的对象相同?拥有比你想象的更多的实例(或只是看错了实例)是导致这类混乱的最常见原因之一。

您的deinit只是打印声明,还是您正在做其他事情?特别是,你在做什么可能会意外地再次留住你吗? (我不记得那是否是明确定义的行为。)

答案 2 :(得分:0)

确保你没有与另一个物体建立任何牢固的关系。想象一下:

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关键字)来解决此问题。

查看Apple的文档here了解更多详情