无法使用 Xcode 仪器定位内存泄漏
Unable to locate memory leak using Xcode Instruments
问题
我早些时候发现我的游戏内存使用量只有在移动方块时才会增加,但它再也不会下降。由此,我可以看出存在内存泄漏。
然后我开始使用 Xcode 仪器,我对它很陌生。所以我关注了 this article 中的很多东西,尤其是 Recording Options
,然后我设置了显示 Call Tree
.
的模式
仪器结果
我需要什么帮助?
我有两个函数,它们只是沿着 row/column 移动所有的图块,然后在最后克隆图块(使用 node.copy()
)所以一切都可以 "loopover",因此项目名称。
我觉得好像磁贴克隆可能会导致一些保留周期,但是,它存储在函数范围内的变量中。在我 运行 克隆上的 SKAction
之后,我使用 copiedNode.removeFromParent()
.
从场景中删除了瓷砖
那么是什么导致了内存泄漏?我可能找错地方了吗?
代码
我已将这段代码缩短到我认为必要的程度。
class 顶部的声明:
/// 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 { [=13=].removeFromParent() }
cloneTiles.removeAll()
/* Moves tiles here */
}
有什么我需要申报的 weak var
之类的吗?我知道保留循环是如何发生的,但不明白为什么它存在于这段代码中,因为我从 cloneTiles
数组中删除了克隆的图块引用。
泄漏发生的大致位置(在 Mark Szymczyk
的帮助下)
这是我在调用堆栈中双击移动瓷砖功能后发生的事情(请参阅下面他的回答):
这证实了内存泄漏是由节点克隆以某种方式引起的,但我仍然不知道为什么这个节点从cloneTiles
数组和场景中删除后仍然保留。节点是否由于某种原因无法从场景中移除?
请留下关于此的任何提示或问题,以便解决此问题!
更多调查
我现在一直在努力掌握 Xcode 仪器,但我仍然很难找到这个内存泄漏。这是可能有帮助的泄漏面板:
即使尝试了 [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 & Roots
图:
感谢您的帮助!
我可以在仪器方面提供一点帮助。如果您双击 Instruments 调用树中的 moveHorizontally
条目,Instruments 将向您显示分配泄漏内存的代码行。 window 的底部是一个调用树按钮。如果单击它,则可以反转调用树并隐藏系统库。这样做将使您更容易在调用树中找到您的代码。
您可以在以下文章中了解有关 Instruments 的更多信息:
我很怀疑你管理复制节点的方式;您可能过早地释放了它,只有保留周期阻止您发现这个错误。但是,让我们专注于打破保留循环。
你要做的是让所有的东西都进入action方法weak
,这样action方法就没有强捕获了。然后在操作方法中,您希望立即保留那些弱引用,这样它们就不会从您的下面消失。这就是所谓的 "weak-strong dance"。像这样:
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
}
}
问题
我早些时候发现我的游戏内存使用量只有在移动方块时才会增加,但它再也不会下降。由此,我可以看出存在内存泄漏。
然后我开始使用 Xcode 仪器,我对它很陌生。所以我关注了 this article 中的很多东西,尤其是 Recording Options
,然后我设置了显示 Call Tree
.
仪器结果
我需要什么帮助?
我有两个函数,它们只是沿着 row/column 移动所有的图块,然后在最后克隆图块(使用 node.copy()
)所以一切都可以 "loopover",因此项目名称。
我觉得好像磁贴克隆可能会导致一些保留周期,但是,它存储在函数范围内的变量中。在我 运行 克隆上的 SKAction
之后,我使用 copiedNode.removeFromParent()
.
那么是什么导致了内存泄漏?我可能找错地方了吗?
代码
我已将这段代码缩短到我认为必要的程度。
class 顶部的声明:
/// 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 { [=13=].removeFromParent() }
cloneTiles.removeAll()
/* Moves tiles here */
}
有什么我需要申报的 weak var
之类的吗?我知道保留循环是如何发生的,但不明白为什么它存在于这段代码中,因为我从 cloneTiles
数组中删除了克隆的图块引用。
泄漏发生的大致位置(在 Mark Szymczyk
的帮助下)
这是我在调用堆栈中双击移动瓷砖功能后发生的事情(请参阅下面他的回答):
这证实了内存泄漏是由节点克隆以某种方式引起的,但我仍然不知道为什么这个节点从cloneTiles
数组和场景中删除后仍然保留。节点是否由于某种原因无法从场景中移除?
请留下关于此的任何提示或问题,以便解决此问题!
更多调查
我现在一直在努力掌握 Xcode 仪器,但我仍然很难找到这个内存泄漏。这是可能有帮助的泄漏面板:
即使尝试了 [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 & Roots
图:
感谢您的帮助!
我可以在仪器方面提供一点帮助。如果您双击 Instruments 调用树中的 moveHorizontally
条目,Instruments 将向您显示分配泄漏内存的代码行。 window 的底部是一个调用树按钮。如果单击它,则可以反转调用树并隐藏系统库。这样做将使您更容易在调用树中找到您的代码。
您可以在以下文章中了解有关 Instruments 的更多信息:
我很怀疑你管理复制节点的方式;您可能过早地释放了它,只有保留周期阻止您发现这个错误。但是,让我们专注于打破保留循环。
你要做的是让所有的东西都进入action方法weak
,这样action方法就没有强捕获了。然后在操作方法中,您希望立即保留那些弱引用,这样它们就不会从您的下面消失。这就是所谓的 "weak-strong dance"。像这样:
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
}
}