I was building a SpriteKit game at work the other day and ran into a couple of annoying issues that were causing memory leaks in my app. The first one was created when I set up the GameScene (an SKScene subclass) in my view controller:
- (void)viewDidLoad { [super viewDidLoad]; SKView * skView = (SKView *)self.view; // Create and present the scene. GameScene * scene = [GameScene sceneWithSize:skView.bounds.size]; scene.scaleMode = SKSceneScaleModeAspectFill; [skView presentScene:scene]; }
There’s one big problem with the code above: the view bounds haven’t been set in viewDidLoad. So, the GameScene size will probably be wrong. No problem, we’ll just move it to where we know the view bounds have been set:
- (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; SKView * skView = (SKView *)self.view; // Create and present the scene. GameScene * scene = [GameScene sceneWithSize:skView.bounds.size]; scene.scaleMode = SKSceneScaleModeAspectFill; [skView presentScene:scene]; }
…except for one thing. Now, the GameScene gets recreated every time you come to the ViewController. If you are doing everything inside SpriteKit, then this isn’t an issue, but my app had several ViewControllers. Each time I left the GameViewController and returned, it would run this code again, creating a new GameScene each time. The old scenes were not being destroyed, so this created a large memory leak on an iPad Mini with iOS7 (about 20% CPU increase each time the GameScene started) that would crash the app after a few games.
The solution is simple:
- (void) viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; SKView * skView = (SKView *)self.view; [skView presentScene:nil]; //remove everything from memory }
This ensures that the GameScene is destroyed when you leave the GameViewController.
This solved most of my problems, but I still had a small memory leak. It wasn’t a big deal and I was tempted not to worry about it, but this game was going to be used in a tradeshow booth, which meant that it would be running for hours at a time. Under those conditions, even a small memory leak could be a big problem.
I was able to isolate the remaining issue to the fact that I set a protocol on my GameScene:
@protocol GameSceneDelegate@required - (void) triggerGameOver; @end
The parent GameViewController implemented the delegate to display a “Game Over” UIView. Even though I was destroying the GameScene, the delegate reference was still not being destroyed. It seems that SpriteKit classes are not reference counted in the same way as standard iOS classes (?). So, I replaced the protocol/delegate with an NSNotification that was sent by my SKScene and picked up by the parent ViewController. Decoupling the 2 classes fixed the memory leak and everything worked as expected.
Oddly, the memory leak only seemed to be an issue in iOS7. iOS8 worked fine. Maybe it was a bug that’s been fixed? As always, I hope maybe this helps prevent a bit of frustration for someone else.