I currently have an Assets singleton class that provides me access to textures, sounds, and music. As my partner and I are going through the memory management stage of our project we have realized that we may have a serious leak being created, and based on my use of Xcode instruments our biggest issue may center around this singleton class. While there are certainly other leaks present, we have noticed that when moving between map screen and game screen back and forth, there is a fairly steady increase of ~100 mb, which appears to correspond to our 11 map assets. With that context, my question is this:
Would the code below create a retain cycle, and if so, can it be managed with the existence of the singleton class, or ought we break this thing up s.t. texture atlases are held separately?
func transitionToMapScreen()
{
//I hope this isn't necessary eventually, but we were trying to ensure all game textures and emitters were deallocated
Assets.sharedInstance.deallocateGameAssets()
gameScene = GameScene()
Assets.sharedInstance.preloadMap
{
[unowned self] in
let mapScene = MapScreen(fileNamed: "MapScreen")!
mapScene.preCreate()
mapScene.scaleMode = self.scaleMode
// Transition with a fade animation
let reveal = SKTransition.fade(withDuration: 2.0)
let fadeMusic = SKAction.run
{
Assets.sharedInstance.bgmTitlePlayer?.setVolume(1.0, fadeDuration: 1.0)
Assets.sharedInstance.bgmTitlePlayer?.play()
Assets.sharedInstance.bgmGamePlayer?.setVolume(0.0, fadeDuration: 1.0)
}
let stopGameMusic = SKAction.run
{
Assets.sharedInstance.bgmGamePlayer?.stop()
}
let transitionAction = SKAction.run
{
self.view?.presentScene(mapScene, transition: reveal)
}
self.run(SKAction.sequence([SKAction.wait(forDuration: 1.0), fadeMusic, SKAction.group([stopGameMusic, transitionAction])]))
} // end Assets.sharedInstance.preloadMap completion block*/
}
From what I understand about retain cycles in Swift, isn't this creating a self reference to the Assets class and creating a memory leak? And could this explain the behavior of our map assets being retained in memory? And if so, what is the proper method of managing this?
I wanted to post this here for those who might be looking for an answer to a similar problem related to hunting down retain cycles to explain memory growth issues. First, thanks very much to all those who helped me halt my neurons' random frantic attempts to find retain cycles where there were none (or were not ones big enough to matter). Now:
First, retain cycles are scary, sure, but using instruments to find them and then managing as Apple recommends since Swift 4.2 is appropriate:
something()
{
[weak self] in
guard let self = self else { return }
self.whatever()
}
I've seen some people argue that you should determine whether unowned or weak makes sense -- this honestly takes the guesswork out and is much easier. I did find unowned crashes are rare anyway at least for us, but I won't opine on your app, and this solves things. Now, then, once you have cleaned house:
We discovered that our memory growth issues stemmed not from our asset singleton class inherently but rather the sheer size of our texture atlases and corresponding overlapping usage of those atlases. I cannot recommend this discussion enough: How does SKTexture caching and reuse work in SpriteKit?. That will explain conceptually the issues you might face with atlases better than I can.
Summarily, however: SpriteKit manages the allocation of your texture atlases, and accordingly you must understand that if you have a very large atlas that is frequently loaded it may not manage it as you expect (I still don't have enough detail to describe that in a better way, but as I said please reference the discussion above as well as Apple's developer guide on SKTextureAtlas: https://developer.apple.com/documentation/spritekit/sktextureatlas).
Now, related to that Apple discussion, I noted this line, which I think really ought to be bolded and in red: "SpriteKit implicitly loads an atlas when one of the atlas's textures is accessed." This was critical to solving what I think was our underlying issue: we had a few places where we accessed textures in the atlas via a single instance for some reason -- you must recognize that in the case of a large atlas SpriteKit will then load your entire massive atlas into memory. So I no longer take lightly Apple's note to manage your atlas sizes. Atlases are meant for assets that are always used together and are going to be drawn together. Loading disparate assets into an atlas was our mistake. We are reorganizing how we manage these accordingly.
Related
I'm coding a generic Swift application (not for iOS, it will later run on raspbian) and i noticed a constant increase of memory. I checked for memory leaks also with the inspector, and there are none.
To dig deeper, I created a blank application for macOS, and I just wrote those lines of code, which are only for testing:
var array = [Decimal]()
while(true) {
array = [Decimal]()
for i in 0..<10000
{
array.append(Decimal(string: i.description)!)
}
sleep(1)
}
As I know, at beginning of every cycle of the while loop the entire array that was filled in the previous cycle should be deleted from memory. But seems that this is not happening, with those lines of code the process memory rises indefinitely.
I also tried the same code on an iOS project putting it on the application function (the one that is called at the beginning in the app delegate) and I noticed that in this case, the memory remains constant and do not rises up.
Am I missing something on the non iOS project?
The Decimal(string:) is creating autorelease objects. Use an autoreleasepool to drain the pool periodically:
var array = [Decimal]()
while true {
autoreleasepool {
for i in 0..<10_000 {
array.append(Decimal(string: i.description)!)
}
sleep(1)
array = []
}
}
Normally the autorelease pool is drained when you yield back to the runloop. But in this case, this while loop never yields back to the OS, and therefore the pool is not getting drained. The use of your own autoreleasepool, like above, solves that problem.
FWIW, Apple has been slowly excising the use of autorelease objects in the Foundation/Cocoa classes. (We used to experience this problem with far more Foundation objects/APIs than we do now.) Clearly Decimal(string:) still is creating autorelease objects behind the scenes. In most practical cases, this isn't a problem, but in your example, you will need to introduce your own autoreleasepool to mitigate this behavior in this while loop.
PROBLEM: Analyzing an iOS app with xcode instruments it appears to be a small but crucial (the snippet is indirectly executed all through the project) memory leak.
DESRCIPTION: We don't make any use of Entities references and follow the typical apple suggested core data object context stack
Here is a screenshot of instruments showing an apparent leak due to NSAsynchronousFetchRequest maintaining a mutual indirect reference to a NSFetchResult
Here is the code snippet triggering the leak (removeing all the non-neccessary code, even the [weak self] still doesn't change it) according to Instruments
let anEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: AnEntity.entityName)
let asyncFetchRequest = NSAsynchronousFetchRequest(fetchRequest: rangeQuantityFetch) { (asynchronousFetchResult) -> Void in
//
}
do {
// commenting out the execute there is no leak anymore
_ = try managedObjectContext.execute(asyncFetchRequest)
} catch {
}
The same thing happens enabling compile optimization flags.
QUESTION: Is there a possibility that it is only an Instruments or xcode bug? Or that it is a known core data issue? Or any useful hint to address me to the problem source would be really appreciated.
It appears to be an issue with the core data object contexts stack. In the configuration we have the a main context having as parent a write context that has the actual persistence coordinator.
According to memory-leak-on-nspredicate-that-used-in-fetch-request-to-fetch-nsmanagedobject the problem is with the main context haveing a parent.
To solve this either
change the core data stack as pointed in the answer
or (seems to
work but is not a clean solution) extend the NSAsynchronousFetchRequest to set to nil the request property
after the completion block is called (this way the reference cycle is
broken and resources can be deallocated by ARC).
i've made a game in Swift and it has a single and a multiplayer modes, and it seems that i have a problem with the memory management, because the app occupies 150 MB and i have no idea why.
the memory debugger of xcode shows that there are 15 issues when i choose a multiplayer game, but in the single player mode it semmes that there are no leaks and still it got 150 MB usage.
here are a screenshot of the debugger in action and i'll appreciate any help with all these triangles, circles and rombs.
thanks in advance!
This looks like a strong reference cycle.
Usually this happens when you don't declare objects that you use in closures as weak. In order to understand what a strong reference cycle is, check the following resources out:
Blog post about strong reference cycles
WWDC videos available (e.g. here and here)
Although the WWDC videos are a little bit older, the main idea is still the same. At least two objects are (transitively) holding a strong reference to each other. When both get released (e.g. when a GameScene is deallocated) they still point to one another, so the system is unable to deallocate them.
Usually, you get a strong reference cycles in the context of closures:
myMethod(...) { (param1, param2) in
self.myVariable = ...
}
If you have a structure like this in your code, try to make self weak. i.e.
myMethod(...) { [unowned self] (param1, param2) in
self.myVariable = ...
}
Since I don't see the code, I can't exactly see where the problem is but it seems that you have a strong reference cycle in your dictionaries. Then it might not be a closure problem.
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.
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.
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
a strong stored property
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.
Ok so awhile back in testing I had a random crash with no error, and I have no clue why. So i went into analyze things and I came up with the following data.
As it would appear my memory usage is getting higher and higher and higher until it sorta plateus. Notice how at the beginning the slope of the general curvature is greater then later on. (as you might notice this is my first time going in and analyzing this sort of thing).
Now what happens in the game is that basically their are two screens.
1. Menu: this screen has quite a few textures but does nothing except has button to play game
2. Game: this has TOOONS of textures and has the bulk of the cpu usage because its the actual game.
3. Death: This screen has one asset, and it is a button that allows you to replay the game. This should not be using much memory OR cpu. However it still has memory. To me this screams whatever a "memory leak" is, is going on.
If you will look at the chart basically what was going on in the game was the menu started, and the first spike was loading up the actual game, then I died. Then from then on I was switching between the Game and Death screens, each spike signals the Game scene being loaded.
If this data were the way I would predict it you would se an oscillation between a very small memory use for the death screen, and then a return to a game memory usage.
Moral of the story is I am pretty sure that sprite kit isn't properly cleaning up after switching scenes and I need to know why if possible.
Btw in order to switch scenes I am using the method made by maxkargin detailed
here
BTW I am working in swift with sprite kit and SKScenes, and SKSpriteNodes
Thanks much!
There is a few reasons why this is, I had a similar problem with my games. If you do it correctly there is no need to remove stuff such as textures. Removing textures on every scene changes is also not ideal, you want to keep them in memory for performance so they do not have to be reloaded each time.
Here is a basic checklist you can use to see if you create a memory leak.
1) Add the deinit method with a print statement to each scene/class. If deinit gets called your scene deallocated correctly.
deinit {
print("Deinit GameScene")
}
2) Are you creating strong reference cycles somewhere by creating references between 2 classes?
The classic Apple example of a strong reference cycle
class Person {
var dog: Dog?
}
class Dog {
var person: Person?
}
To fix it you would have to make 1 of those 2 properties weak
class Person {
var dog: Dog?
}
class Dog {
weak var person: Person?
}
Also good practice with optionals is to set them to nil when they are no longer needed.
person = nil
Maybe check google and the Apple Swift documentation on how to deal with this. I also asked a similar question a while back
Swift SpriteKit ARC for dummies
3) Are you using closures? They can cause memory leaks.
A more common scenario in SpriteKit is these 2 examples which could/will cause a memory leak and makes your scene to not deallocate. (action 2 is a closure which captures self)
// Example 1
let action1 = SKAction.wait(forDuration: 1)
let action2 = SKAction.run(someMethod)
let sequence = SKAction.sequence([action1, action2])
run(SKAction.repeatForever(sequence))
// Example 2
let action1 = SKAction.wait(forDuration: 1)
let action2 = SKAction.run {
self.someMethod()
}
let sequence = SKAction.sequence([action1, action2])
run(SKAction.repeatForever(sequence))
A good rule of thumb is that when the compiler forces you to use self than you most likely will create a memory leak without using weak/unowned.
So to fix the above 2 SKAction examples you could either make sure you always remove all actions when you change scenes or IMO even better would be to change your code to this to avoid creating a memory leak in the first place.
let action1 = SKAction.wait(forDuration: 1)
let action2 = SKAction.run { [weak self] in
self?.someClassMethod()
}
let sequence = SKAction.sequence([action1, action2])
run(SKAction.repeatForever(sequence))
Note in all those above example you could also write
.... { [unowned self] in
and than you would not need to use the ? in the closure
self.someMethod()
When you use unowned you basically say self will never be nil which could cause a crash if it is actually nil when the closure is called. If you use weak self you tell the compiler self might become nil before the closure is called therefore self is an optional to avoid the crash.
I think it is almost always better to use "weak" instead of "unowned" to avoid this. In one of my games I was using unowned self in a closure that was called when it fetched StoreKit products from iTunes. This caused me subtile crashes because I could exit the SKScene before the closure was called. If you use weak self you will not crash because you use optionals.
Hope this helps a bit
sprite kit uses cache to retain perforemce between scenes that why switching between scenes spike up memory and this memory does not release at any case either you remove all children from scene or [self.view presentScene:nil]; their are better solution for this...
read this article carefully
https://developer.apple.com/library/ios/qa/qa1889/_index.html
manage scene with view's and remove SKView's from those view's to maintain memory in multi screen game
So based on what everyone said this is what I did. Before changing to a new scene I run functions that basically takes all textures, and sprite nodes and sets them to an empty constructor so that they are smaller, then I remove all actions, and children. This has seemed to do the trick! Their might still be other memory problems, however I need to look more carefully and tweak some things before I can be sure.
Thanks for the advice everyone!
As a side-dish answer, I had a complex scene with a lot of custom sprite kit subclasses and actions interacting together, and the only thing that prevented my SKScene subclass from calling its deinit was one delegate I forgot to define as weak or unowned, which created a classic strong reference cycle problem. So if you assigned any delegates to self in your scene's setup like this:
myCustomNode.delegate = self
Then check the definition of the delegate, it should look like this:
weak var delegate:MyCustomProtocol?
or this:
unowned var delegate:MyCustomProtocol?
If you do not have access to myCustomNode's source code, then try assigning the delegate to nil in your scene like this:
override function willMove(from view:SKView) {
myCustomNode.delegate = self
}
Also, if you get the error
weak / unowned may only be applied to class and class-bound protocol types
This is because your custom protocol must include : class like this:
protocol MyCustomProtocol : class {/*protocol definition here*/}