My SpriteKit game has three scenes for now; Menu.m
, LevelSelect.m
and Level.m
. When I start the app, the memory usage is 35MB. Upon transitioning from the main menu to the level selection scene it pretty much stays around 35MB. Moving from the level select scene to the level itself it shoots up to around 150MB. Granted, there are more sprites and classes involved in creating the level. However, when I reload the level via an overlay menu that I have as a sprite the memory usage continues to rise by around 2MB for each reload. Could it be a retain cycle issue? The same is true if I switch back to the level select scene and then re-enter the level itself, the memory continues to climb and climb. I am concerned about my implementation of my state machine. I'll post some skeletal code below:
LevelScene.m
-(void)didMoveToView:(SKView *)view {
...
[self initGameStateMachine];
...
}
...
//other methods for setting up the level present
...
-(void) initGameStateMachine {
LevelSceneActiveState *levelSceneActiveState = [[LevelSceneActiveState alloc] initLevelScene:self];
LevelSceneConfigureState *levelSceneConfigureState = [[LevelSceneConfigureState alloc] initLevelScene:self];
LevelSceneFailState *levelSceneFailState = [[LevelSceneFailState alloc] initLevelScene:self];
LevelSceneSuccessState *levelSceneSuccessState = [[LevelSceneSuccessState alloc] initLevelScene:self];
NSMutableDictionary *states = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
levelSceneActiveState, @"LevelSceneActiveState",
levelSceneConfigureState, @"LevelSceneConfigureState",
levelSceneFailState, @"LevelSceneFailState",
levelSceneSuccessState, @"LevelSceneSuccessState",
nil];
_gameStateMachine = [[StateMachine alloc] initWithStates:states];
[_gameStateMachine enterState:levelSceneActiveState];
}
-(void)update:(CFTimeInterval)currentTime {
//update the on screen keyboard with notes that are being played
[_gameStateMachine updateWithDeltaTime:[NSNumber numberWithDouble:currentTime]];
}
-(void) willMoveFromView:(SKView *)view {
[self removeAllChildren];
}
StateMachine.m
#import "StateMachine.h"
#import "GameState.h"
@interface StateMachine()
@property NSMutableDictionary *states;
@end
@implementation StateMachine
-(instancetype)initWithStates:(NSMutableDictionary*)states {
if (self = [super init]) {
for (id key in [states allValues]) {
if (![key conformsToProtocol:@protocol(GameState)]) {
NSLog(@"%@ does not conform to @protocol(GameState)", key);
return nil;
}
}
_states = states;
}
return self;
}
//this method will be used to start the state machine process
-(bool)enterState:(id)nextState {
if (!_currentState) {
_currentState = [_states objectForKey:[nextState className]];
[[NSNotificationCenter defaultCenter] addObserver:_currentState selector:@selector(keyPressed:) name:@"KeyPressedNotificationKey" object:nil];
return YES;
}
else if ([_currentState isValidNextState:nextState]) {
[_currentState performSelector:@selector(willLeaveState)];
_currentState = [_states objectForKey:[nextState className]];
[[NSNotificationCenter defaultCenter] addObserver:_currentState selector:@selector(keyPressed:) name:@"KeyPressedNotificationKey" object:nil];
return YES;
}
return NO;
}
-(void)updateWithDeltaTime:(NSNumber*)currentTime {
[_currentState performSelector:@selector(updateWithDeltaTime:) withObject:currentTime];
}
@end
LevelSceneActiveState //this is one of the 4 states
#import "LevelSceneActiveState.h"
#import "LevelSceneConfigureState.h"
#import "LevelSceneSuccessState.h"
#import "LevelSceneFailState.h"
#import "StateMachine.h"
#import "LevelScene.h"
#import "SSBitmapFontLabelNode.h"
@interface LevelSceneActiveState()
@property LevelScene *levelScene;
@end
@implementation LevelSceneActiveState
-(instancetype)initLevelScene:(LevelScene *)levelScene {
if (self = [super init])
_levelScene = levelScene;
return self;
}
-(void)updateWithDeltaTime:(NSNumber*)currentTime {
//game variables created here
....
//state machine needs to be set here...if set in init, it does not have a value in the LevelScene yet
if (_gameStateMachine == nil)
_gameStateMachine = _levelScene.gameStateMachine;
//game logic performed here
...
//check for timer finishing
if (!_levelScene.timer.isValid) {
//success
if (_levelScene.score >= 7) {
[_gameStateMachine enterState:LevelSceneSuccessState.self];
}
else { //failure
[_gameStateMachine enterState:LevelSceneFailState.self];
}
}
}
//another class is used to trigger notifications of key presses
-(void) keyPressed:(NSNotification*)notification {
NSNumber *keyCodeObject = notification.userInfo[@"keyCode"];
NSInteger keyCode = keyCodeObject.integerValue;
if (keyCode == 53)
[self escapePressed];
}
-(void) escapePressed {
[_gameStateMachine enterState:LevelSceneConfigureState.self];
[_levelScene childNodeWithName:@"timer"].paused = YES;
}
-(bool)isValidNextState:(id)nextState {
if ([[nextState className] isEqualToString:@"LevelSceneConfigureState"])
return YES;
...
return NO;
}
//this makes sure that we're not notifying an object that may not exist
-(void) willLeaveState {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:@"KeyPressedNotificationKey"
object:nil];
}
@end
When switching between states I do not want the LevelScene
scene to go away. I understand that this is hard to diagnose especially when you're not sitting in front of the full project. What can I do to self-diagnose this myself? Any helpful tips/tricks would be great.
[UPDATE] I tried the Product->Profile->Instruments->Allocation thing, but I have no idea what to do with it. The memory is indicating that it continues to rise though.