简体   繁体   中英

Unable to locate memory leak using Xcode Instruments

The problem

I discovered earlier that my memory usage in my game was only going up when the tiles were moved, but it never went back down again. From this, I could tell there was a memory leak.

I then started using Xcode Instruments, which I am very new to. So I followed many things from this article , especially the Recording Options , and then I set the mode to show the Call Tree .

Results of Instruments

仪器-泄漏测试结果

What do I need help with?

I have two functions that just move all the tiles along that row/column, and then clones the tile at the end (using node.copy() ) so everything can "loopover", hence the project name.

I feel as if the tile cloning may be causing some retention cycle, however, it is stored in the variable within the function scope. After I run the SKAction on the clone, I remove the tile from the scene using copiedNode.removeFromParent() .

So what may be causing this memory leak? Could I be looking in the wrong place?


Code

I have shortened this code to what I consider necessary.

Declaration at the top of the 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]()

Cloning of the tile within the moving tiles functions:

/// 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()
}

Function to move tiles immediately:

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

    /* Moves tiles here */
}

Is there something I need to declare as a weak var or something? I know how retain cycles occur, but do not get why it exists in this code, as I remove the cloned tile reference from the cloneTiles array.


Roughly where the leak is occurring (helped by Mark Szymczyk )

Here is what happened after I double-clicked on the move tiles function in the call stack (refer to his answer below):

仪器-查找内存泄漏

This is confirming that the memory leak is caused somehow by the node clone, but I still don't know why this node is still being retained after it is removed from the cloneTiles array and the scene. Could the node be having trouble getting removed from the scene for some reason?

Please leave any tips or questions about this, so this problem can be solved!

More investigating

I have now been trying to get to grips with Xcode Instruments, but I am still really struggling to find this memory leak. Here is the leaks panel which may help:

仪器-泄漏面板

乐器-泄漏的历史

Even after trying [weak self] , I still had no luck:

工具-在闭包内使用[弱自我]

Even the leaks history still looks the same with the [weak self] within the closure.

Continuing to try to resolve the reference cycle

Currently, @matt is helping me with this issue. I have changed a few lines of code, by adding things like [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
}

Unfortunately, I could not make copiedNode a weak property as it would always be instantly nil , and unowned caused a crash about the reference being read after being deallocated. Here is also the Cycles & Roots graph if this is helpful:

仪器-周期和根图


Thank you for any help!

I can help a little on the Instruments front. If you double-click the moveHorizontally entry in the Instruments call tree, Instruments will show you the lines of code that are allocating the leaked memory. At the bottom of the window is a Call Tree button. If you click on that, you can invert the call tree and hide system libraries. Doing that will make it easier to find your code in the call tree.

You can learn more about Instruments in the following article:

Measuring Your App's Memory Usage with Instruments

I'm rather suspicious of the way you're managing the copied node; you may be releasing it prematurely, and only the retain cycle was preventing you from discovering this mistake. However, let's concentrate on breaking the retain cycle.

What you want to do is make everything coming into the action method weak , so that there is no strong capture by the action method. Then in the action method you want to immediately retain those weak references so they don't vanish out from under you. That's called the "weak-strong dance". Like this:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM