3

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:

retain cycle shown here

Here is the notorious closure #3, however:

gameScene.animateMatchedCircles() {
    [weak self] in self?.animateMatchedCirclesCompletion()
}
Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
Mike Pandolfini
  • 532
  • 4
  • 17
  • 2
    So are you sure this is the closure that’s causing the persistent strong references? The "malloc stack” feature and the “debug memory graph” features can identify the source of these issues unambiguously, if you haven’t done that already. – Rob Jan 29 '19 at 16:19
  • 1
    Thanks, Rob. I honestly am not sure, but I'm dealing with hundreds of closures that look like this one in what was a giant function written by another coder on my team with no attention paid to memory management at the time. I'm trying to get a general rule for memory management as something here is leaking hundreds of mb, and I haven't seen any examples other than [weak self] and [unowned self] here. The leaks shown in the memory graph seem to point to some retain cycle in this function, but I've already weak self and unowned self everywhere possible. – Mike Pandolfini Jan 29 '19 at 16:23
  • 1
    Agreed, but I've already done this. Let me take some screenshots and add them to show you what I'm dealing with more precisely, but everywhere it's pointing already has handled the weak self case. – Mike Pandolfini Jan 29 '19 at 16:28
  • 1
    I added the memory graph that seems to be the retain cycle of concern in my question. animateMatchedCircles() is a monster that I'd rather not copy and paste here, but some general idea of where to start with something like this when as far as I can tell I have managed self appropriately is what I'm looking for. Could another variable be captured, and what is the appropriate way to manage that? [weak circle] in, e.g., in the instance I provided above? – Mike Pandolfini Jan 29 '19 at 16:53
  • 2
    For the simplest case, is the `initialBlock` itself a strong property? If not, you don't need to bother with reference cycles. – nayem Jan 29 '19 at 16:54
  • 2
    And to answer the ___Could another variable be captured, and what is the appropriate way to manage that?___ I recommend you read the section [Resolving Strong Reference Cycles for Closures](https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html) – nayem Jan 29 '19 at 16:56
  • 1
    Thanks, nayem. initialBlock is an SKAction.run run block in that specific case. These retain self unless you use [unowned self], so my current presumption is that other variables can be retained by them besides self, and other closures (which may not be SKAction run blocks) can also retain other variables. My fundamental question is how to manage these "other variables" as most sources I have read only refer to the "self" case. – Mike Pandolfini Jan 29 '19 at 17:00
  • 1
    Thanks, nayem. I have read this, and it suggests to me that it would be appropriate to do what I'm doing, as other variables can be captured other than self in a closure. This causes other issues for me as I cannot make certain variables weak NOR can I use weak-strong dance, but I can attempt to work around those issues. – Mike Pandolfini Jan 29 '19 at 17:10
  • 2
    Yeah, right. But see, `SKAction.run` is a type method that occupies a closure. Referencing `self` inside a type method doesn't necessarily create reference cycle. In order for it to create a cycle, self would also have to have a reference to the closure (which isn't your case for the `initialBlock` I guess). Otherwise there is no reference cycle. You may have seen something with `DispatchQue.main.async { self.doSomething() }` or even `UIView.animate...` things.They also don't create reference cycles. – nayem Jan 29 '19 at 17:41
  • 1
    That makes sense. Is it possible that if I were doing self.run(SKAction.sequence([...that SKAction runblock somewhere in here...])) that would create a strong reference since self would then reference the closure? I noticed the guy originally working on this code used self.run in lieu of just "run(". – Mike Pandolfini Jan 29 '19 at 18:06
  • 1
    Another possibility is that circle references self -- we use circles' positions on self to do some other things. I wonder if that's the reference cycle. I guess this is why I would need to somehow make circle weak without making circle weak. Maybe separating and using another object referencing the original circle and making that weak is the right move. – Mike Pandolfini Jan 29 '19 at 18:15
  • 2
    As long as self doesn't hold a reference to that SKAction.seuence (by declaring a property to hold that sequence), there could be no potential reference cycle. Same goes for SKAction.run – nayem Jan 29 '19 at 18:40
  • 1
    Thanks so much for your help. I'm a bit at a loss in that case with where this reference cycle is coming from in that case, but I will try to formulate this with another question to avoid perpetuating the comment thread here. – Mike Pandolfini Jan 29 '19 at 18:50

0 Answers0