2

I make a bunch of sprites from the gameScene and other objects, all by calling a function that creates instances of a subclass of SKSpriteNode.

eg, from GameScene, like this:

let sizee = CGSize(width: 100, height: 100)
let posi = CGPoint(x: 200, y: 300)

func createSprite(){
    let new = Circle(color: SKColor.blue, size: sizee , position: posi)
    addChild(new)
    new.zPosition = 2
}

from within Circle, I remove each instance after its done a bit of animating:

import SpriteKit

class Circle: SKSpriteNode {

    let fade = SKAction.fadeAlpha(to: 0.5, duration: 2)
    let myScale = SKAction.scaleX(to: 3, duration: 2)
    let die = SKAction.removeFromParent()

    override init(texture: SKTexture?, color: UIColor, size: CGSize) {
        super.init(texture: texture, color: color, size: size)
    }
    convenience init(color: UIColor, size: CGSize, position: CGPoint) {
        self.init(color: color, size: size)
        self.position = position
        self.run(SKAction.sequence([fade, die]))
        self.run(myScale)
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

Is this enough to ensure that these are getting released from memory?

Or do I have to do something else to flush them from the game world and memory?

Confused
  • 6,048
  • 6
  • 34
  • 75

3 Answers3

5

Few weeks ago Apple have update this page:

Swift uses Automatic Reference Counting (ARC) to track and manage your app’s memory usage. In most cases, this means that memory management “just works” in Swift, and you do not need to think about memory management yourself. ARC automatically frees up the memory used by class instances when those instances are no longer needed.

Reference counting only applies to instances of classes. Structures and enumerations are value types, not reference types, and are not stored and passed by reference.

There is a way to prevent an instance from deinitialising, and thus creating a leak. This is called a Strong Reference Cycle. Take a look at this answer where you can understand in detail what I mean now.

Looking to your code I would have done this:

self.run(SKAction.group([fade, myScale], completion:  {
     [weak self] in
     guard let strongSelf = self else { return }
     strongSelf.removeFromParent()
}))
Community
  • 1
  • 1
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
  • 2
    the strong self is not even needed, weak self works perfectly (nil.removeFromParent() will not do anything.) – Knight0fDragon Oct 30 '16 at 08:19
  • 1
    @Knight0fDragon If you're confusing 'if let' with 'guard let', guard goes on if condition is satisfied, in this case strongSelf is self and is not nil so you can remove it from parent. But if you're referring to weak self only, OP want to remove self at the end of the animation, not when the scene will deallocated. – Alessandro Ornano Oct 30 '16 at 08:37
  • 1
    I am not confusing anything `self.run(SKAction.group([fade, myScale], completion: { [weak self] in self?.removeFromParent() }))` is all you need, there is no point to retain it, if it is dead, removeFromParent won't get called – Knight0fDragon Oct 30 '16 at 08:39
  • The same instructions. But in your case you always call removeFromParent(), me only if self is not nil. – Alessandro Ornano Oct 30 '16 at 08:45
  • no, it just gets ignored at runtime, nothing is actually called. I think the only difference besides less lines of code for elegence, is your way adds to the reference count, then removes from the count on return. – Knight0fDragon Oct 30 '16 at 08:50
  • Yes, about RC that's true but I'm sorry, I don't remember if your "optional chaining" is valid for SKScene itself (it should..) and it's "safe" for the compiler so I can not express myself in complete safety. – Alessandro Ornano Oct 30 '16 at 09:05
  • Another thing about "optional chaining" is that: if you have a group of instructions inside the block with 'self?', compiler should pass to each instructions and decide to ignore it, guard is simply exit before it happen. – Alessandro Ornano Oct 30 '16 at 09:12
  • how is optional chaining not safe? it does almost exactly what your guard is doing, but as an if call. if NOT NULL perform message else continue execution which in this case, return. your code is if NULL return else perform message and return – Knight0fDragon Oct 30 '16 at 09:13
  • I know how guard works, if you were doing other things besides returning, I would't have commented, but it isn't. You are rewriting @Confused code and telling him how to avoid a retain cycle. I was improving your answer by avoiding any retain whatsoever in your completion block – Knight0fDragon Oct 30 '16 at 09:15
  • Your first comment is not exaclty the same as your last ;) – Alessandro Ornano Oct 30 '16 at 09:21
4

Yes, I think so.

There is basically one thing that you need to do to check whether something's memory will be released:

Check if there are any other objects which hold strong references to the object that you're checking.

In this case, you want to check whether your Circles will be released from memory. Let's see what objects strongly reference Circle, starting from the creation of a circle.

  1. When the circle is created, the new variable holds a strong reference. This reference will break after the method createSprite returns.

  2. When the circle is added to the scene, the scene holds a strong reference to the circle. This reference will be broken after the circle is removed from the scene.

Other than those, I can't see any other strong references to the circle object. So you should be fine.

Also, in Xcode 8, there's a new memory debugging feature. You can use it to check this too!

Just run your app, and click this:

enter image description here

Then go to the main editor area, you should see this ("TestingArea" is the name of my app):

enter image description here

Now you can see all the instances of the objects you created by clicking on the button which is on the right of your app's name. In my case, it's the button on the right of "TestingArea" i.e. "AppDelegate":

enter image description here

If you see Circle in that list, that means your app has a Circle object somewhere. If not, no Circle is in memory!

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • From a programmatic point of view, there is nothing else @Confused has to do to ensure objects are released. It is more about best practices and ensuring strong references don't exist, using the techniques you've described to verify everything is Ok. – Mark Brownsword Oct 28 '16 at 20:53
  • @MarkBrownsword this is one thing I am definitely worried about. Am I avoiding any strong references by doing things this way. I have NO IDEA about best practices. And am probably an expert in worst practices. – Confused Oct 29 '16 at 00:57
  • @MarkBrownsword to make my point (of ignorance) clearer, I've been doing research, and can't ascertain if my function call to create an instance is a weak reference, strong reference or only internal to the life of the function. NO clue. completely confused, too. – Confused Oct 29 '16 at 02:37
  • @Sweeper, part of the reason this is all troubling me, is I want to believe this: "When the circle is created, the new variable holds a strong reference. This reference will break after the method createSprite returns." but I don't know this for sure, other than that I avoided returning an instance so there wouldn't be another link created. Have I done the right thing in not returning an instance, and making the "die" internal to the instance I want to kill off, and out of memory? – Confused Oct 29 '16 at 02:40
  • 1
    @Confused you can be sure that the reference will break if you don't use the created circle anywhere else just in that method. The circle will just simply take care of itself with this approach. – Daniel Oct 29 '16 at 04:34
  • @Confused I think returning isn't a problem. As long as you don't assign `new` to a property in a class, it will be released from memory eventually when you remove the node. If you *do* want to assign it to a property, that's fine too, just remember to set it to some other value e.g. nil after you finish using it. This way the reference can be broken. – Sweeper Oct 29 '16 at 06:54
  • @sweeper, what do I set to nil to get rid of it, and break the reference? And, in your first screenshot, there's four items to choose from. Sorry, what do I click on to bring up this drop down? – Confused Oct 29 '16 at 14:53
  • @Confused at the moment you don't need to set anything to nil because you're not assigning `new` to a property. Go to the 6th tab on the right pane. You will see your app's name and two buttons on the right. Click The button on the right. The drop-down will be opened. Then press "view memory graph hierarchy" – Sweeper Oct 29 '16 at 14:58
  • @Sweeper, sorry, yes, I know I don't need `nil` now, but I'm going to, very soon, as I'm about to start throwing things around between classes, and beginning to try to think in terms of relationships. Slow going, I'll admit. I draw much and do little. – Confused Oct 30 '16 at 01:38
  • @Confused ok, so the basic principle is this: when you want to release an object from memory, set the variable holding that instance to nil. But you also need to watch out for retain cycles: always check if a class A holds an instance of class B and at the same time, B also holds an instance of A. This is when you need to make one of them hold a "weak" reference. For more info, Google "swift retain cycles". – Sweeper Oct 30 '16 at 07:10
  • Does this mean (the use of `nil`) that variables referencing these objects need to be `optionals` so they can accept `nil` at some point in the future? – Confused Oct 30 '16 at 08:05
  • Yes exactly! That's one of the uses of optionals @Confused – Sweeper Oct 30 '16 at 08:07
  • Wow. I never realised `optionals` are useful for this process of ensuring releasing of objects via assigning `nil`. This is an eyeopener. And helps me think positively of `optionals`, for the first time EVER! I've HATED these things from the first time to the billionth time I've tried to understand their purpose and reasoning for existence and constance annoyance of everything I tried to do. There will still be some time to adapt to understanding and finding any joy with casting. Toxic mess. But, for now THANK YOU!!!! – Confused Oct 30 '16 at 08:09
3

It is obviously enough, although self.run(myScale) does not make any sense, because the Sprite has already been removed from your scene.

Also, I suggest to use a different approach:

self.run(fade, completion: {
  self.removeFromParent()
})

This shows more explicitly what happens after the action finished.

Daniel
  • 357
  • 2
  • 11
  • The use of the `self.run(myScale)` in this manner is a lazy way of making a group. The `scale` begins at exactly the same time as the `fade and die` sequence, and is set to run for the same duration as the fade. So it creates a look/behaviour the same as running scale and fade in a group, without needing to make a group. – Confused Oct 29 '16 at 00:56
  • Alright, but what happens if you have to add another action to the group? What happens if you need different timing? Then you also add another line and adjust timing? Not to mention readability. I think it really worsen it and this approach just simply not clean. If you want to see group behaviour, then make a group. This kind of lazyness usually just lead to unnecessary refactor in a short time. – Daniel Oct 29 '16 at 04:27
  • What happens if I spend all my time thinking about how I could or should design code architectures to avoid possible refactoring for future possibilities? – Confused Oct 29 '16 at 05:07
  • more seriously, this is a demo, for here. It's not the real project, just a scratchpad for figuring out how retain, release, reference and instancing works with regards actions, instances, etc. It's my code, it took me no time to think about, takes me no time to read, and serves to remind me that starting actions creates a fall through to the next action, immediately. – Confused Oct 29 '16 at 05:09
  • 1
    Alright then, these are just suggestions. But believe me, if you try to make the clean coding conventions as a habit, it will be easier for you to read them as well after a certain amount of time :) Also thanks to ARC, you don't need to worry much here about releasing etc. because you don't hold any unnecessary strong references that would cause a memory leak. Your approach is absolutely correct in this manner regarding memory management. – Daniel Oct 29 '16 at 05:27
  • THANK YOU. That's very comforting to know. I'm no CS grad. At this point I don't care about cleanliness of code because I'm having much more trouble figuring out how things work, relate and communicate than anything else. Clean code is a luxury for later, when I can conceive of what I'm doing, and do it. – Confused Oct 29 '16 at 05:48
  • Just one more comment to clarify things in my view. Clean code is not a luxury, it is a must. The real luxury is to write slappy code and waste time with unnecessary refactoring. From business side, that's the real luxury. But anyways, I get that this is just playing around. Good luck with SpriteKit. – Daniel Oct 29 '16 at 18:09