使用Xcode Instruments

时间:2019-01-27 19:50:13

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

问题

我早些时候发现我的游戏内存使用量只是在移动磁贴时才增加,但再也没有回落。由此,我可以判断出内存泄漏。

然后我开始使用Xcode Instruments,这是我的新手。因此,我遵循了this article中的许多内容,尤其是Recording Options,然后设置了显示Call Tree的模式。

仪器结果

Instruments - Results of testing for leaks

我需要什么帮助?

我有两个功能,它们仅沿该行/列移动所有图块,然后在末尾克隆图块(使用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数组中删除了克隆的tile引用,所以不知道为什么它存在于代码中。


大概在哪里发生泄漏(由Mark Szymczyk帮助)

这是我在调用堆栈中双击“移动磁贴”功能后发生的情况(请参见下面的答案):

Instruments - Finding the memory leak

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

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

更多调查

我现在一直在尝试掌握Xcode Instruments,但是我仍然很努力地寻找这种内存泄漏。这是泄漏面板,可能会有所帮助:

Instruments - Leaks panel

Instruments - Leak's history

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

Instruments - Using [weak self] within the closure

即使封包内的[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 & Roots图:

Instruments - Cycles & Roots graph


谢谢您的帮助!

2 个答案:

答案 0 :(得分:1)

我可以在乐器方面提供一些帮助。如果您在仪器调用树中双击{{1}}条目,仪器将向您显示分配泄漏内存的代码行。窗口底部是“呼叫树”按钮。如果单击该按钮,则可以反转调用树并隐藏系统库。这样做将使在调用树中查找代码更加容易。

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

Measuring Your App's Memory Usage with Instruments

答案 1 :(得分:1)

我对您管理复制节点的方式非常怀疑;您可能会过早释放它,并且只有保留周期才阻止您发现此错误。但是,让我们集中精力打破保留周期。

您要做的是将所有内容都放入动作方法weak中,以使动作方法不会引起任何强烈的捕获。然后,在操作方法中,您要立即保留那些弱引用,以免它们从您的下方消失。这就是所谓的“弱劲舞”。像这样:

    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
        }
    }