2

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.

crashoverride777
  • 10,581
  • 2
  • 32
  • 56
  • Not all classes cause reference cycles, it just depends on how they reference each other that cause this to occur. In a project I am working on I came across code that was not releasing. What I did was add a deinit { print("NameOfClass") } to each class, so that I could work out what was being released and what was not. It is time consuming. Also always make sure if you use any delegates that these properties are always weak. – totiDev Mar 04 '16 at 14:45
  • 1
    Even if this is an interesting topic, you should ask questions one by one. It is really hard to answer and cover all of your concerns, especially without fully workable examples which can reproduce described behaviour. You should concentrate on one particular problem (eg. why Platform objects are not deallocated properly) Like others pointed, there are some methods / best practices to follow, and of course you can use Instruments (Leaks), but still, the real code is needed for a precise answer. – Whirlwind Mar 04 '16 at 16:38

2 Answers2

1

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

Every strong reference to an object could become part of strong reference cycles. It could be

  1. a strong stored property
  2. a strong local/global variable/constant

With strong I mean it is not declared as weak or unowned.

Infact if for a given moment there is a strong reference path from an object to the object itself then the cycle is created and ARC will not free the memory for all the objects in the cycles.

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

There's no more to say. When the object A has a strong reference to the object B... which has a strong reference to A you have a strong reference cycle.

A few suggestions:

Keep track of the deinitialization

Just add a deinitializer to your class and check on the console that your objects do get deinitialized when you expect it

class Foo: SKSpriteNode {
    deinit {
        print(String(self))
    }
}

Let SpriteKit manage the strong references

Avoid strong references between nodes in your scene graph. Use instead the methods and computed properties provided by the SKNode class to retrieve other nodes like.

.scene
.parent
.childNodeWithName(...)
.childrean

This approach could lead to some performance issue but you should first make you code correct and then try to improve the performance.

Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
1

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.

Community
  • 1
  • 1
crashoverride777
  • 10,581
  • 2
  • 32
  • 56