SpriteKit: SKTileMap leaks with `SKTexture(rect: CGRect)` usage

Hello reader,

I am facing an issue that I am not able to resolve. I have been able to create a demo project that demonstrates the issue, which I hope enables you to have a look as well and hopefully find a way to resolve it.

What is the issue:

I am using SKTileMapNode in order to draw tile maps. Instead of using the tilesets as you can use from within the Xcode editor, I prefer to do it all programmatically using tilesheets (for a plethora of reasons that I will leave out of this equation).

This is the code of the gameScene:

import SpriteKit
import GameplayKit

class GameScene: SKScene {

    private let tileSize = CGSize(width: 32, height: 32)

    override func didMove(to view: SKView) {
        super.didMove(to: view)

        let tileSet = createTileSet()
        let tileMap = SKTileMapNode(tileSet: tileSet,
                                    columns: 100,
                                    rows: 100,
                                    tileSize: tileSize)

        for column in 0..<tileMap.numberOfColumns {
            for row in 0..<tileMap.numberOfRows {
                guard let tileGroup = tileSet.tileGroups.randomElement() else {
                    fatalError()
                }
                tileMap.setTileGroup(tileGroup, forColumn: column, row: row)
            }
        }
        addChild(tileMap)
    }

    private func createTileSet() -> SKTileSet {
        let tileSheetTexture = SKTexture(imageNamed: "terrain")
        var tileGroups = [SKTileGroup]()
        let relativeTileSize = CGSize(width: tileSize.width/tileSheetTexture.size().width,
                                      height: tileSize.height/tileSheetTexture.size().height)
        for idx in 0...2 {
            for jdx in 0...2 {
                let tileTexture = SKTexture(rect: .init(x: CGFloat(idx) * relativeTileSize.width,
                                                        y: CGFloat(jdx) * relativeTileSize.height,
                                                        width: relativeTileSize.width,
                                                        height: relativeTileSize.height),
                                            in: tileSheetTexture)
                let tileDefinition = SKTileDefinition(texture: tileTexture,
                                                      size: tileSize)
                let tileGroup = SKTileGroup(tileDefinition: tileDefinition)
                tileGroups.append(tileGroup)
            }
        }

        let tileSet = SKTileSet(tileGroups: tileGroups)
        return tileSet
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        presentSceneAgain()
    }

    func presentSceneAgain() {
        if let frame = view?.frame {
            view?.presentScene(GameScene(size: frame.size),
                               transition: .doorsCloseHorizontal(withDuration: 1.0))
        }
    }
}

This demo project create a tilemapnode of 100 X 100 tiles. Then, it fills these 10.000 tiles with a random tile from the tilesheet named "terrain.png". This tile sheet contains many tiles, but I only take the 9 tiles (3 X 3) from the lower left corner as a random tile option.

Thus, the 10.000 tiles get filled with one of these 9 tiles. So it doesnt look pretty or anything, but that isnt the purpose.

Now, to create these 9 tile textures, I use the SKTexture(rectIn:) method on the source texture being "terrain.png".

I think the code is quite clear in itself, but so far the explanation. When you run it, you should see the map being rendered. When you tap the scene, the scene will present a new instance of the scene. Not more than that.

Now, when you do this, have a look at the RAM usage of the app. You will see it steadily increases over time, each time you click the scene and a new scene is presented.

I looked deeper into what is happening, and what I see in the memory graph, is that for every present of the scene that is done, there are 3 SKTexture instances being created that are never released. The first time the scene is rendered, there 11 SKTexture instances allocated (I dont know why there are 11 though. I would expect 10: the source texture and the 9 tile textures).

But then as mentioned, after a tap and a new present, I get 14 SKTexture, of which 3 are zombies, see image leak_1.

Moreover, Xcode reports multiple additional leaks from Jet and Metal allocations, see image leak_all.

As far as I know, the code presented is not retaining any references that it should not, and I suspect this leaks are happening somewhere inside SpriteKit. But I am not able to find exactly where, or how to resolve it.

I hope someone can help with this issue.

Here are all the leaks reported by Xcode after a new instance of the scene is presented:

For reference, the project is also available here:

https://github.com/sanderfrenken/sktilemap-rect-in

SpriteKit: SKTileMap leaks with `SKTexture(rect: CGRect)` usage
 
 
Q