【问题标题】:Unable to locate memory leak using Xcode Instruments无法使用 Xcode Instruments 定位内存泄漏
【发布时间】:2019-06-20 21:07:37
【问题描述】:

问题

我之前发现我在游戏中的内存使用量仅在移动瓷砖时才会增加,但它再也没有下降。由此,我可以看出内存泄漏。

然后我开始使用 Xcode Instruments,我对它非常陌生。所以我从this article关注了很多东西,尤其是Recording Options,然后我把模式设置为显示Call Tree

仪器结果

我需要什么帮助?

我有两个函数可以沿着该行/列移动所有图块,然后在最后克隆图块(使用node.copy()),这样所有东西都可以“循环”,因此项目名称。

我觉得磁贴克隆可能会导致一些保留周期,但是,它存储在函数范围内的变量中。在克隆上运行 SKAction 后,我使用 copiedNode.removeFromParent() 从场景中删除图块。

那么可能是什么导致了这种内存泄漏?我是不是找错地方了?


代码

我已将此代码缩短为我认为必要的部分。

类顶部的声明:

/// Delegate to the game scene to reference properties.
weak var delegate: GameScene!
/// All the cloned tiles currently on the board.
private var cloneTiles = [SKSpriteNode]()

在移动磁贴函数中克隆磁贴:

/// A duplicate of the current tile.
let copiedNode = currentTile.node.copy() as! SKSpriteNode // Create copy
cloneTiles.append(copiedNode) // Add as a clone
delegate.addChild(copiedNode) // Add to the scene
let copiedNodeAction = SKAction.moveBy(x: movementDifference, y: 0, duration: animationDuration) // Create the movement action

// Run the action, and then remove itself
copiedNode.run(copiedNodeAction) {
    self.cloneTiles.remove(at: self.cloneTiles.firstIndex(of: copiedNode)!)
    copiedNode.removeFromParent()
}

立即移动图块的功能:

/// Move all tiles to the correct location immediately.
private func moveTilesToLocationImmediately() {
    // Remove all clone tiles
    cloneTiles.forEach { $0.removeFromParent() }
    cloneTiles.removeAll()

    /* Moves tiles here */
}

有什么我需要声明为weak var 之类的吗?我知道保留周期是如何发生的,但不明白为什么它存在于这段代码中,因为我从 cloneTiles 数组中删除了克隆的磁贴引用。


泄漏发生的大致位置(Mark Szymczyk 帮助)

这是我双击调用堆栈中的移动图块功能后发生的事情(请参阅下面的回答):

这是确认内存泄漏是由节点克隆以某种方式引起的,但是我仍然不知道为什么从cloneTiles数组和场景中删除该节点后仍然保留它。该节点是否因某种原因无法从场景中移除?

请留下任何关于此的提示或问题,以便解决此问题!

更多调查

我现在一直在尝试掌握 Xcode Instruments,但我仍然很难找到这个内存泄漏。这是可能有帮助的泄漏面板:

即使尝试了[weak self],我仍然没有运气:

即使泄漏历史看起来仍然与闭包中的[weak self] 相同。

继续尝试解决引用循环

目前,@matt 正在帮助我解决这个问题。我更改了几行代码,添加了[unowned self]

// Determine if the tile will roll over
if direction == .up && movementDifference < 0 || direction == .down && movementDifference > 0 {
    // Calculate where the clone tile should move to
    movementDifference -= rollOverDistance

    /// A duplicate of the current tile.
    let copiedNode = currentTile.node.copy() as! SKSpriteNode // Create copy
    cloneTiles.append(copiedNode) // Add as a clone
    delegate.addChild(copiedNode) // Add to the scene
    let copiedNodeAction = SKAction.moveBy(x: 0, y: movementDifference, duration: animationDuration) // Create the movement action

    // Run the action, and then remove itself
    copiedNode.run(copiedNodeAction) { [unowned self, copiedNode] in
        self.cloneTiles.remove(at: self.cloneTiles.firstIndex(of: copiedNode)!).removeFromParent()
    }

    // Move the original roll over tile back to the other side of the screen
    currentTile.node.position.y += rollOverDistance
}

/// The normal action to perform, moving the tile by a distance.
let normalNodeAction = SKAction.moveBy(x: 0, y: movementDifference, duration: animationDuration) // Create the action
currentTile.node.run(normalNodeAction) { [unowned self] in // Apply the action
    if forRow == 1 { self.animationsCount -= 1 } // Lower animation count for completion
}

不幸的是,我无法将copiedNode 设为weak 属性,因为它总是会立即成为nil,并且unowned 导致在被释放后读取引用的崩溃。如果有帮助,这也是Cycles &amp; Roots 图表:


感谢您的帮助!

【问题讨论】:

  • 看看这个:stackoverflow.com/questions/27007350/… 您需要在运行 SKAction 中引用的所有外部事物都是弱的。所以不仅仅是自我,也可能是复制的节点。
  • 整个问题似乎是这种奇怪的方式,您坚持对复制的节点的额外引用并且还让节点自行删除。我觉得这非常令人困惑和奇怪。我很难对此提出积极的建议,但我建议在 run 动作中使用弱强舞蹈,以便 if 值到达动作处理程序,它们被保留为 local 强变量。
  • @matt 是否有另一种方法可以推荐,将克隆节点存储在cloneTiles 数组中?
  • @matt 我也不确定你所说的“弱强舞”是什么意思。你的意思是我从动作中删除[unowned self, copiedNode],然后在里面写let copiedNodeRef = copiedNode
  • @matt 上面的注释做了可选绑定,这是否意味着我需要将copiedNode 声明为weak var?非常感谢您的帮助,这意义重大!

标签: ios xcode memory-management memory-leaks xcode-instruments


【解决方案1】:

我很怀疑您管理复制节点的方式;你可能会过早地释放它,只有保留周期才能阻止你发现这个错误。但是,让我们集中精力打破保留循环。

你要做的就是让一切进入action方法weak,这样action方法就没有强捕获了。然后在 action 方法中,你想立即保留那些弱引用,这样它们就不会从你下面消失。这就是所谓的“弱强舞”。像这样:

    copiedNode.run(copiedNodeAction) { [weak self, weak copiedNode] in
        if let `self` = self, let copiedNode = copiedNode {
            // do stuff here
            // be sure to log so you know we arrived here at all, as we might not
        }
    }

【讨论】:

  • 请注意,我不必包含weak self,我只需要weak copiedNode
  • 确实,您发布的保留周期图似乎证实了这一点,该图显示了指向自身的节点。
  • 我仍然不明白这些图表,但我想我会在适当的时候学习 :p 我几乎在 Swift 一年,因为我从 2 月中旬开始去年。
  • 这也让我很恼火,我遇到的一些问题(像这样)需要很长时间才能弄清楚,而且只有 3 个字!很痛苦,但你绝对应该得到赏金(当我可以奖励它时)。
  • 在 Swift 4 中,您不必再用反引号引用本地“self”了。
【解决方案2】:

我可以在仪器方面提供一些帮助。如果您双击 Instruments 调用树中的 moveHorizontally 条目,Instruments 将向您显示分配泄漏内存的代码行。窗口底部是一个调用树按钮。如果单击它,您可以反转调用树并隐藏系统库。这样做会更容易在调用树中找到您的代码。

您可以在以下文章中了解有关 Instruments 的更多信息:

Measuring Your App's Memory Usage with Instruments

【讨论】:

  • 非常感谢仪器的帮助!我现在将使用您提供的帮助来更新问题,以检测分配泄漏内存的行。不过,我会在更新问题之前再试一次,看看我是否能自己弄清楚。再次感谢您的大力帮助! ?
猜你喜欢
  • 1970-01-01
  • 2011-10-10
  • 2023-04-10
  • 2017-05-26
  • 2016-01-10
  • 2012-05-30
  • 2011-08-02
  • 2011-06-28
  • 2011-06-07
相关资源
最近更新 更多