I have encountered some memory leaks that appear to be related to closures capturing variables not necessarily what appears to be the well-known "self retain cycles" in my code. My question in particular relates to SKAction run blocks, e.g.
let initialBlock = SKAction.run {
[unowned self] in
emitter.position = self.circlesLayer.convert(circle.currentPosition, to: self)
emitter.zPosition = 150
emitter.isPaused = true
self.addChild(emitter)
}
This is subsequently used like:
run(SKAction.sequence([SKAction.wait(forDuration: 0.25), initialBlock, emitterStart, SKAction.wait(forDuration: 1.0), emitterStop]))
Here, I add an emitter after a short delay, unpause it (emitterStart), wait, and then remove it (emitterStop).
To elaborate further, emitter is declared as:
let emitter = Assets.sharedInstance.getEmitter(.specialcircleexplosion)
where Assets is a singleton that pulls in the specific emitter of interest, as I preload these entering the game scene.
To elaborate EVEN FURTHER (I'm sorry), circle is an instance of another class as well. Should I also think about [unowned circle]?
Is there any reason in an SKAction run block to [weak emitter] or [unowned circle] as well as [weak self] or [unowned self]? This is a bit double-barreled, but in a more general sense: should I be watching for retain cycles generally other than the classic weak self case?
Some additional memory graph info, as I mentioned in comments to Rob below:
Here is the notorious closure #3, however:
gameScene.animateMatchedCircles() {
[weak self] in self?.animateMatchedCirclesCompletion()
}