【问题标题】:Do protocols have an effect on the retain count?协议对保留计数有影响吗?
【发布时间】:2019-10-07 04:38:07
【问题描述】:

我有一个非常简单的代码。我有意与委托人创建一个内存循环。尝试观察和学习如何使用 Xcode 的 Memory Graph。

我不明白为什么在连接部分,Xcode 说有 3 个连接。应该只有 2 个。

如果我创建一个带有闭包的内存循环,那么它将显示 2 个连接。

我的委托泄露代码:

protocol SomeDelegate {
    func didFinishSomething()
}

class Something {
    var delegate: SomeDelegate?
}

class ViewController: UIViewController, SomeDelegate {

    var x = Something()

    override func viewDidLoad() {
        super.viewDidLoad()
        print("view did load")
        x.delegate = self
    }

    func didFinishSomething() {
        print("something was finished")
    }

    deinit {
        print("dellocated!!!")
    }
}

我将该视图推送到导航控制器中,然后返回。

这 2 个委托对象的内存地址略有不同,仅相差 +16


这似乎与委托对象作为协议有关。因为当我删除协议时,它减少到 2

class Something2 {
    var delegate: ViewController?
}

class ViewController: UIViewController {

    var x = Something2()

    override func viewDidLoad() {
        super.viewDidLoad()
        print("view did load")
        x.delegate = self
    }

    deinit {
        print("dellocated!!!")
    }
}

【问题讨论】:

  • 我很确定 Xcode 只是混淆了——使用协议值不应该导致任何额外的保留,它应该像普通的强引用一样执行保留。
  • 这似乎是让 Xcode 困惑的可选性 - 尝试使用 gist.github.com/hamishknight/26edbfd8531c9c3c49391cfb44c4d9c7,内存图现在正确显示 2 个连接。
  • 谢谢。我用了你的代码。将var delegate: SomeDelegate 更改为var delegate: SomeDelegate?,然后显示2。这仍然是您的预期吗?如果它是可选的,那么我想它应该回到 3...
  • 嗯,我想这不是可选性,必须是其他一些因素。有趣的是,在命令行项目中运行的最小化示例中,分配的顺序似乎会影响结果:gist.github.com/hamishknight/52c616c7402df2cf23fa781aea19e590。无论根本原因是什么,我认为 Xcode 有问题,而不是 Swift。
  • 哈!你能把你的sn-ps放在答案中吗?以为我们还没有得出结论,有时答案是“它有问题”

标签: swift memory-management memory-leaks protocols


【解决方案1】:

我很确定这只是 Xcode 弄糊涂了。协议值不应导致任何额外的保留超过正常的强引用,因为内存管理操作通过存储在存在容器中的类型元数据转发到底层值。

这是一个在 Xcode 的内存图调试器中重现相同结果的最小示例:

protocol P {}

class C : P {
  var d: D?
}

class D {
  var p: P?
}

func foo() {
  let c = C()
  let d = D()
  c.d = d
  d.p = c
}

foo()

print("insert breakpoint V")

print("insert breakpoint ^")

如果您在打印语句之间插入断点并查看内存图,您将看到 3 个连接。有趣的是,如果在分配d.p 之后分配c.d,您将看到2 个连接的正确结果。

但是,如果我们在 swift_retainswift_release 上设置符号断点以查看强大的保留/释放 Swift ARC 流量(同时打印出存储在 %rdi 寄存器中的值,这似乎是使用的寄存器在 x86 上传递参数):

然后在调用foo()之后立即插入一个断点,我们可以看到在这两种情况下,每个实例都获得+2保留和-2释放(记住它们以+1保留的形式进入世界,因此保持他们分配):

swift_retain 1
     rdi = 0x000000010070fcd0
swift_retain 2
     rdi = 0x000000010070fcd0
swift_release 1
     rdi = 0x0000000000000000
swift_release 2
     rdi = 0x000000010070fcd0
swift_retain 3
     rdi = 0x00000001007084e0
swift_retain 4
     rdi = 0x00000001007084e0
swift_release 3
     rdi = 0x00000001007084e0
swift_release 4
     rdi = 0x000000010070fcd0
swift_release 5
     rdi = 0x00000001007084e0

所以看起来 Xcode 出了问题,而不是 Swift。

【讨论】:

【解决方案2】:

是的,用于实现协议类型变量的存在容器可以生成额外的保留。有关各种实现细节,请参阅Understanding Swift Performance。 16 字节(2 个机器字)是存在容器的标头。

【讨论】:

  • 谢谢罗伯。我会观看视频并阅读“存在容器”,然后回复您。我目前对这些一无所知
  • 我看了视频。我会再看一遍。它很有意思。只是没有找到关于存在容器、额外保留、机器词的提及。有没有你发现更相关的具体时间?
  • 对不起;这两个视频的标题非常相似。正确的是理解 Swift 性能(更新链接)
  • 我观看了部分视频。我想你说的是this moment。这是否意味着任何具有超过 2 个属性的具体类都有一个堆分配?顺便说一句,您是否有机会看到 Hamish 的回答和问题?他似乎承认你在说什么,但不理会它。
  • 我对任何与 Swift 实现细节相关的工作假设是 Hamish 比我知道的更多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-21
  • 2012-01-03
  • 2016-06-28
  • 2020-11-05
  • 1970-01-01
  • 2015-04-24
相关资源
最近更新 更多