协议对保留计数有影响吗?

Do protocols have an effect on the retain count?

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

我不明白为什么在连接部分,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!!!")
    }
}

我将该视图推送到 navigationController,然后返回。

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!!!")
    }
}

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

我很确定这只是 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 上设置符号断点以查看强 retain/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。