Reputation: 4066
So I am developing an app that includes a SpriteKit game. What I noticed is that after I start and close the game the memory keeps increasing as shown on the graph below:
I am presenting the game from a View Controller using a modal presentation. So the question is: Is there a way to completely remove the scene from memory?
I am tapping a close button on the View Controller to dismiss it. The scene.quitGame()
method basically deallocates all nodes, actions etc. In an ideal world I wouldn't even need to do any of that as there would be a magic command that gets rid of the SKScene
completely.
@IBAction func quit(_ sender: UIButton) {
scene.quitGame()
scene.removeFromParent()
self.removeFromParentViewController()
self.dismiss(animated: true, completion: nil)
scene = nil
}
EDIT: I present the scene from viewDidLoad with the following code:
scene = GameScene(size: view.bounds.size)
let skView = view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
skView.ignoresSiblingOrder = true
scene.scaleMode = .resizeFill
if let heroImage2 = heroImage2, let myImage = myImage, let monsterImage = monsterImage{
scene.addPicture(img: heroImage2, myImage: myImage,monsterImage: monsterImage, bigMonsterImage: bigMonsterImage!)
}
skView.presentScene(scene)
scene.viewController = self
EDIT 2 - I commented out the call to this method that includes a repeat forever action and the leak seems to go away
func createHerosAction(duration: Double = 5.0){
run(SKAction.repeatForever(
SKAction.sequence([
SKAction.run(addHero),
SKAction.wait(forDuration: duration)
])
), withKey:"createHeros")
}
func addHero() {
// Create sprite
if(heroImage == nil){
heroImage = UIImage(named: "hero")
}
let Texture = SKTexture(image: heroImage!)
let hero = SKSpriteNode(texture:Texture)
hero.size.width = size.width * 0.17
hero.size.height = hero.size.width
hero.name = "hero"
hero.zPosition = 2
let heroSide = arc4random_uniform(2)
hero.physicsBody = SKPhysicsBody(rectangleOf: hero.size) // 1
hero.physicsBody?.isDynamic = true // 2
hero.physicsBody?.categoryBitMask = PhysicsCategory.Hero // 3
hero.physicsBody?.contactTestBitMask = PhysicsCategory.Projectile // 4
hero.physicsBody?.collisionBitMask = PhysicsCategory.None // 5
let actualY = random(min: size.height*0.3 + hero.size.height/2 + size.height * 0.12, max: size.height - hero.size.height/2 - size.height * 0.15)
let pointRightOfScreen = CGPoint(x: size.width + hero.size.width/2, y: actualY)
let pointLeftOfScreen = CGPoint(x: -hero.size.width/2, y: actualY)
if(heroSide == 0){
hero.position = pointRightOfScreen
}else{
hero.position = pointLeftOfScreen
}
addChild(hero)
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(5.0))
var actionMove = SKAction()
if(heroSide == 0){
actionMove = SKAction.move(to: pointLeftOfScreen, duration: TimeInterval(actualDuration))
}else{
actionMove = SKAction.move(to: pointRightOfScreen, duration: TimeInterval(actualDuration))
}
let numberOfUpsAndDown = random(min: CGFloat(1.0), max: CGFloat(10.0))
let variance = random(min: CGFloat(-70.0), max: CGFloat(70.0))
let mvHero = moveHeroUpAndDown(node: hero, variance: variance,duration: actualDuration/numberOfUpsAndDown)
let actionMoveDone = SKAction.removeFromParent()
let loseLifeAction = loseLife(node: hero)
hero.run(SKAction.sequence([actionMove, loseLifeAction, actionMoveDone]), withKey: "moveHero")
hero.run(mvHero, withKey: "UpAndDown")
}
EDIT 3: The leak seems to be on this SKAction that gets called above. After I removed this action the leak stopped.
func loseLife(node: SKSpriteNode) -> SKAction{
let loseALifeAction = SKAction.run(){
if(self.gamePaused == false){
self.playSound(fileName: "losesound.wav")
if(self.lives > 0){
self.updateLives(newValue: self.lives - 1, lostLife: true)
//self.lives -= 1
}
print("lives: \(self.lives)")
if(self.lives <= 0){
print("hit me")
node.run(self.loseGame())
}
}
}
return loseALifeAction
}
Upvotes: 2
Views: 554
Reputation: 13675
Based on your comments, you should use capture lists:
func createHerosAction(duration: Double = 5.0){
run(SKAction.repeatForever(
SKAction.sequence([
SKAction.run {[unowned self] in
self.addHero()
},
SKAction.wait(forDuration: duration)
])
), withKey:"createHeros")
}
It is a big topic, but I suggest you to start reading the link I have posted, because it has everything explained in detail starting from "How ARC works", "What are Capture Lists" to the weak and unowned keywords. I wrote few times about this already, as well as many others on this site, and it is a big topic, so I will skip that this time :)
Upvotes: 5
Reputation: 18561
I believe its an internal SpriteKit leak holding the ViewController in memory. We found that SpriteKit appeared to retain the Scene and SKView after dismissing the viewController.
In our case we began specifically presenting a nil scene when the game was over, right after we presented the game over screen.
// Remove the game scene to stop game code and free up memory
if let skView = view as? SKView {
skView.presentScene(nil)
}
Upvotes: 0