Search code examples
swiftreferencesprite-kitinstrumentscycle

Swift SpriteKit ARC for dummies


I have been trying to wrap my head around strong reference cycles and I am struggling. I have been reading the documentation from apple and on some website and I feel like they dont really address my issues.

I understand you have to use weak and unowned depending if the object can be nil or not. So say you have to 2 classes like this

class Person {
   var dog: Dog?
  ....
}

class Dog {
  weak var person: Person?
}

that reference each other I know that one of them has to use weak/unowned. This is the classic example seen in most tutorials.

I also have seen examples like this and they too make sense to me

class Person {

 unowned let gameScene: GameScene

 init(scene: GameScene) {
   self.gameScene = scene 
  ....
 }

I also understand that NSTimers can cause strong reference cycles if not invalidated. I am not using NSTimers so this should not be a problem.

I furthermore understand that protocols can cause memory leaks too, so they way to handle them would be to make it a class protocol

protocol TestDelegate: class { }

and where I am trying to reference the protocol make it a weak property

 class: SomeClass {

  weak var myDelegate: TestDelegate?

 }

And finally I know about closures where I am capturing self like this

SKAction.runBlock { [unowned self] in
   self.player.runAction....
}

However when it comes to my actual spritekit game I seem to have reference cycles all over the place, even for simple classes I am sure dont reference another class, and I dont understand why.

So my main questions are

1) Do all properties create strong reference cycles or only global properties that are created before a class is initialised?

2) What other things could potentially create a strong reference cycle in my simple classes?

For example I am using a class that creates platforms

class Platform: SKSpriteNode {

 /// basic code to create platforms 
 /// some simple methods to rotate platforms, move them left or right with SKActions.

Now I have similar classes like this for Traps, Enemies etc. Again they normally just set the sprite properties (physics body etc) and have some methods to animate them or rotate them. There is nothing fancy going on in them, especially in regards to referencing other classes or scenes.

In my gameScene I have a method to create platforms (would be same for enemies, traps etc)

func createPlatform() {

 let platform1 = Platform(.....
 platformNode.addChild(platform1)

 let platform2 = Platform(....
 platformNode.addChild(platform2)

 let platform3 = Platform(...
 platformNode.addChild(platform3)

 // platform node is just a SKNode in the gameScene to help maintain the difference zPositions of my objects.

}

When I am running allocations I can see that only 1 of the 3 platforms goes into transient state, 2 stay persistent when I change to my menuScene. Whats strange is that always only 1 gets removed, doesnt matter if I change the order or create/delete some platforms. So it seems they are creating strong references, except 1. So if I replay my level a few times I can quickly have 50-100 persistant platforms in memory. My scene therefore also doesn't get deinit which is more memory wasted.

Another example I have is

class Flag {
  let post: SKSpriteNode
  let flag: SKSpriteNode

  init(postImage: String, flagImage: String) {

       post = SKSpriteNode(imageNamed: postImage)
       ...


       flag = SKSpriteNode(imageNamed: flagImage)
       ...
       post.addChild(flag)
  }
}

and same problem. I create some flags in my scenes and sometimes a flag doesnt get removed, sometimes it does. Again this class does not reference any scene or custom class, it merely creates a sprite and animates it.

In my scene I have a global property for flag

 class GameScene: SKScene {

   var flag: Flag!

  func didMoveToView... 

 }

Why would this create a strong reference if flag itself doesnt reference the GameScene?. Also I can't use

weak var flag: Flag!

because I get a crash once the flag gets initialised.

Is there something obvious I am missing when doing this? Is there a good trick to find them in Instruments because it seems madness for me. It just confusing to me mainly because my classes are quite simple and dont reference other custom classes, scenes, viewControllers etc.


Solution

  • Thank you for all the answers, especially appzYourLift and your detailed reply.

    I was digging into my spriteKit project the whole day, also experimenting with playground a lot and my game is totally leak free now with no strong reference cycles to be found.

    It actually turned out a lot of the leaks/persistant classes I was seeing in instruments where simply there because something else didnt deallocate.

    Its seems that repeat action forever was causing my leaks. All I had to do is loop through all the nodes in my scene and remove their actions.

    This question helped me with this

    iOS 7 Sprite Kit freeing up memory

    UPDATE: I recently revisited this topic again because I felt like having to manually remove actions on nodes should not be necessary and could become cumbersome. After more research I found the precise issue for the (my) memory leaks.

    Having a "SKAction repeat forever" like this apparently causes the leak.

    let action1 = SKAction.wait(forDuration: 2)
    let action2 = SKAction.run(someMethod) 
    let sequence = SKAction.sequence([action1, action2])
    run(SKAction.repeatForever(sequence))
    

    So you need to change it to this to not cause a leak

     let action1 = SKAction.wait(forDuration: 2)
     let action2 = SKAction.run { [weak self] in
         self?.someMethod()
     } 
     let sequence = SKAction.sequence([action1, action2])
     run(SKAction.repeatForever(sequence))
    

    This is coming directly from an Apple bug report I made.