Macos SpriteKit保留周期或内存泄漏

Macos SpriteKit保留周期或内存泄漏,macos,memory-leaks,sprite-kit,retain-cycle,Macos,Memory Leaks,Sprite Kit,Retain Cycle,我的SpriteKit游戏目前有三个场景Menu.m,LevelSelect.m和Level.m。当我启动应用程序时,内存使用量是35MB。从主菜单过渡到关卡选择场景时,它几乎保持在35MB左右。从“级别选择”场景移动到级别本身时,它会发射到150MB左右。当然,在创建关卡时会涉及到更多的精灵和类。然而,当我通过一个覆盖菜单重新加载关卡时,每次重新加载的内存使用量将继续增加2MB左右。这可能是一个保留周期问题吗?同样的道理,如果我切换回关卡选择场景,然后重新进入关卡本身,内存将继续爬升。我担心我

我的SpriteKit游戏目前有三个场景
Menu.m
LevelSelect.m
Level.m
。当我启动应用程序时,内存使用量是35MB。从主菜单过渡到关卡选择场景时,它几乎保持在35MB左右。从“级别选择”场景移动到级别本身时,它会发射到150MB左右。当然,在创建关卡时会涉及到更多的精灵和类。然而,当我通过一个覆盖菜单重新加载关卡时,每次重新加载的内存使用量将继续增加2MB左右。这可能是一个保留周期问题吗?同样的道理,如果我切换回关卡选择场景,然后重新进入关卡本身,内存将继续爬升。我担心我的状态机的实现。我将在下面发布一些骨架代码:

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];
}
#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
状态机.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];
}
#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//这是四个州之一

#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
在状态之间切换时,我不希望
LevelScene
场景消失。我知道这很难诊断,尤其是当你没有坐在整个项目前面的时候。我自己能做些什么自我诊断呢?任何有用的提示/技巧都会很好


[更新]我试过产品->配置文件->仪器->分配,但我不知道该怎么做。内存指示它继续上升。

仪器教程文章

对于像我这样在使用Xcode的工具时一窍不通的人,这里有一个来自Ray Wenderlich的例子,它真的让人哑口无言

我在我的项目中发现了太多的问题,甚至因为那篇文章我都不认为是问题。我并不感到惊讶,没有人发布关于这个问题的答案,因为当你有这些类型的问题时,他们对你正在进行的项目非常私人,并且很难调试

我的问题+解决方案

我的问题很常见。当我的场景重新加载时,我正在一次又一次地加载一组资源,在本例中是一个.sf2(声音字体)文件。我真的以为我和我所有的精灵都解决了这个问题

下面是我如何使用仪器找到它的:

  • 在Xcode中,转到
    产品
    ->
    配置文件
    ,然后选择
    分配
此窗口应弹出:

  • 单击左上角的
    红色圆圈
    按钮(这将启动您的应用程序)
  • 在应用程序中执行可能导致问题的操作,然后按
    标记生成
  • 重复导致问题的操作,然后继续按
    标记生成

    注意:
    markgeneration
    会在此时拍摄应用程序的快照,以便您可以查看内存中的更改
标记生成按钮“>



  • 停止应用程序运行,然后进入其中一代(我选择了C代,因为那是内存使用差异变得恒定的时候) 标记生成按钮多次“>
  • 我的采样器(一个
    avaudionitsampler
    对象)显示为
    SamplerState
    正在分配大量内存

  • 我点击了
    samplestate
    右侧的小箭头,它将我带入了这个视图(显示了大量分配给内存的项目)

  • 单击其中一个,再单击扩展详细信息按钮将允许您查看此项的堆栈跟踪

  • 我双击了我认为是问题所在的方法

  • 双击该方法后,它会显示另一个视图,其中包含您的代码以及分配最多内存的代码行的百分比(非常有用!)
    注意:在我的例子中,罪魁祸首在我每次重新加载关卡时分配了大约1.6MB的内存!忽略注释掉的代码,我在修复问题后拍摄了保存的会话的屏幕截图。

  • 在修复我的代码后,不再有任何主要的内存分配。尽管我还有一些事情要清理!只有33KB之间的水平重新加载,这是更好的!