SpriteKit TileMapNode MemoryLeak Bug

Hello community,


after days and days of researching and trying out my best, i do not find a solution on my memory leak problem.

I load my GameScene.sks with this code like everyone else does:


extension SKNode {

  1. class func unarchiveFromFile(file:String) -> SKNode? {
  2.   if let path = Bundle.main.path(forResource: file, ofType: "sks") {
  3.      let url = URL(fileURLWithPath: path)
  4.           do {
  5.              let sceneData = try Data(contentsOf: url, options: .mappedIfSafe)
  6.              let archiver = NSKeyedUnarchiver(forReadingWith: sceneData)
  7. archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
  8.              let scene = archiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey)
  9.                   as! SKNode                        
  10.              archiver.finishDecoding()                   
  11.              return scene
  12.              }
  13.           catch { print(error.localizedDescription) return nil }
  14.     } else {
  15.        return nil
  16.     }
  17. }


and i am using it in my code here:


  1. func load(level: String) {
  2.      if let levelNode = SKNode.unarchiveFromFile(file: level) {
  3.      mapNode = levelNode
  4.      self.worldLayer.addChild(mapNode)
  5.      loadTileMap()
  6.      }
  7. }


If i comment out the line

self.worldLayer.addChild(mapNode)
everything works perfect and memory will never rise. But if i use it (i need it :D) memory keeps climbing and climbing.

With use of Instruments it says that the line with

let scene = archiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as! SKNode
causes all the memory leaks.

Unfortunately i do not get it managed to remove my leaks.


I appreciate all help.

Accepted Reply

This thread has been deleted

I reduced your code a bit, and changed the logic slightly to detach the tile map node from the unarchived scene, then added it to the GameScene. I also checked via logging that none of the SKScene (or subclass) objects was leaked. Then I ran the code in Instruments and saw a pair of leaks every time the .sks file was unarchived. These were not leaked objects, but leaked malloc buffers, so this is not a reference cycle problem but an under-release problem.


It certainly looks to me that this is a bug in SpriteKit. You should submit a bug report.


A couple of points about your code:


— I removed the "self.removeFromParent()" line from both scene subclasses. I don't know if a scene has a parent, but if it does you probably shouldn't mess with it, especially when the scene is moving into or out of a view.


— The scene in the .sks file should have its custom class removed in the inspector. The .sks file I was using had it set to GameScene (presumably because I started from an Xcode template that's set up that way), but since you're not really using it as a scene (just as a place to get the tile node map) you don't want to run any of the GameScene setup code by accident.


Here's the code I was using for the test:


class GameViewController: UIViewController {
  override func viewDidLoad() {
       super.viewDidLoad()
       if let view = self.view as! SKView? {
            let scene = MenuScene()
            scene.size = view.bounds.size
            scene.scaleMode = .aspectFill
            view.presentScene(scene)
            view.ignoresSiblingOrder = true
            view.showsFPS = false
            view.showsNodeCount = false
       }
  }
}
class MenuScene: SKScene {
  override func didMove(to view: SKView) {
       layoutView()
  }
  override func willMove(from view: SKView) {
       self.removeAllActions()
       self.removeAllChildren()
  }
  deinit {
       print("MenuScene Deinit")
  }
  func layoutView() {
       return
  }
  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
       let scene = GameScene()
       scene.size = self.size
       scene.scaleMode = .aspectFill
       view?.presentScene(scene)
  }
}
class GameScene: SKScene {
  override func didMove(to view: SKView) {
       if let levelNode = SKNode(fileNamed: "Level1") {
            if let tiles = levelNode.childNode(withName: "Tiles") as? SKTileMapNode {
                 tiles.removeFromParent()
                 self.addChild(tiles)
            }
       }
  }
  override func willMove(from view: SKView) {
       self.removeAllActions()
       self.removeAllChildren()
  }
  deinit {
       print("GameScene Deinit")
  }
  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
       let scene = MenuScene()
       scene.size = self.size
       scene.scaleMode = .aspectFill
       view?.presentScene(scene)
  }
}

Replies

>> With use of Instruments it says that the line with

let scene = […]
causes all the memory leaks.


No, that's not the cause of the leaks. That's the place where the leaked objects are created. Based on your code, the cause of the leak seems to be your "load" function. You add a new child of "worldLayer", but you don't remove it when moving to a new level. So, all of your levels exist in the scene at one time, and memory usage goes up. You need something like this:


func load(level: String) {
     if let levelNode = SKNode.unarchiveFromFile(file: level) {
          mapNode.removeFromParent () // discard the old level
          mapNode = levelNode
          self.worldLayer.addChild(mapNode)
          loadTileMap()
     }
}


If you are, in fact, removing the old level from "worldLayer" somewhere else, then the leak indicates you still have a reference to it somewhere in your code.

Hello and welcome to the forums.

>it says that the line with

let scene = archiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as! SKNode
causes all the memory leaks
.


...only points to where the issue bubbled up, not it's root cause. Set break points and step thru your code to help isolate, and/or brush up on the process according to Apple via the Instruments User Guide 'Find Memory Leaks': https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/FindingLeakedMemory.html


Speak up if more questions, and good luck.


Ken

Hello and thank you for your answer. I forgot to say that i already implemented removing the child and everything that belongs to it.


override func willMove(from view: SKView) {

mapNode.removeFromParent()

worldLayer.removeAllActions()

worldLayer.removeAllChildren()

worldLayer.removeFromParent()

mapNode = nil

worldLayer = nil

self.removeAllActions()

self.removeAllChildren()

self.removeFromParent()

}


But unfortunately this is not working for me. The leak still exists.


Setting `mapNode.removeFromParent ()` before initializing it cannot work because it will have nil. I tried it anyways and this was the expected result.

I don´t know why removing the child in `willMove` doesn´t do it for me.


Greetings

Apple still didn‘t reply to my bug report... :/