Xcode 仪器中的泄漏仪器准确吗?

Is the leak instrument in Xcode instruments accurate?

我有以下代码,应该表明存在内存泄漏。但当我用 Xcode 泄漏仪测试它时实际上并没有。 这是否意味着实际上没有内存泄漏,或者仪器可能有误?

private func initTimePublisher() {
        timer.publish(every: tickInterval, on: runloop, in: runloopMode)
            .autoconnect()
            .map { self.durationCalculator.activeDuration(between: self.startDate!, and: [=11=]) }
            .assign(to: \.activeDuration, on: self) // This should be a retain cycle which causes a memory like right?
            .store(in: &subs)
}

您的程序可以有一个保留周期而不会发生内存泄漏。

您的程序有一些“根”值:全局变量、堆栈上的变量和 thread-local 变量。您的程序可以“从头开始”访问这些值中的任何一个,而无需从其他 object.

引用它

然后你的程序有其他 objects 作为根 objects 的属性可达:根的“children”。它有 objects 可以从 children 的属性到达:根的“grandchildren”。等等。一些 object 可以从根通过多条路径到达。

因此您的程序有一组可达的 objects:根及其所有后代。

任何 object 您的程序已分配但未取消分配且不再在可达集中的任何内容都已泄漏。

现在让我们看一下您问题中的代码。

  • self是不是泄露了?你没有说 self 是什么。如果它是从全局变量引用的,或者它在视图(或视图控制器)层次结构中,或者以其他方式可访问,则它不会泄漏。

  • 发布者泄露了吗?你用 Timer.publish(...).autoconnect().map{...}.assign(...) 建立的发布者没有泄露。它包含对 self 的引用,但在 initTimePublisher returns.

  • 时被销毁
  • 你储存在subsAnyCancellable是不是泄露了?如果 self 没有泄漏,那么 AnyCancellable 是可达的,所以它也没有泄漏。

现在,您可能会感到困惑,因为我说过发布者在 initTimePublisher returns 时被销毁。但是您仍然收到来自计时器的信号,对吗?

当您调用 .assign(to:on:)(或 .sink)时,您创建了一个 Subscription。事实上,您创建的 Subscription object 图表通常反映了您订阅的 Publisher object 图表。这是您的 Publisher 图表:

Timer.Publisher <- Autoconnect <- Map

因此,当您使用 .assign 运算符订阅时,您会得到一个 Subscription 图表,如下所示:

Timer.Publisher.Subscription <- Autoconnect.Subscription <- Map.Subscription
    <- Assign.Subscription

(在幕后,Combine 通常对 Subscription 类型使用 ConduitInner 之类的名称,因此这就是您在内存检查器或调试中看到的内容打印。)

您从 .assign 返回的 AnyCancellable 存储在 subs 中,是 Assign.Subscription.

的包装器

因此,订阅图中存在保留循环。 Map.Subscription object 包含您传递给 .map 的闭包,其中包含对 self 的引用。 Assign.Subscription 包含对 self 的引用和对 Map.Subscription 的引用。并且 self 包含 subs,其中包含包裹 Assign.SubscriptionAnyCancellable

但是所有这些object仍然是可达的,没有泄漏,只要self是可达的。

如果您从 subs 中删除 AnyCancellable 并让它被销毁,那么它会在 Assign.Subscription 上调用 cancel。这告诉 Assign.Subscription 放弃其对 self 的引用并取消其上游订阅 Map.Subscription。然后 Map.Subscription 丢弃它对闭包的引用并取消它的上游订阅。等等。一旦取消一直传播到 Timer.Publisher.Subscription,所有的保留周期都被打破并且所有的 Subscription object 都被清除了。没有泄漏。