Освободить SKScene после перехода на другой SKScene в SpriteKit

У меня есть контроллер вида, в котором есть три skscenes в качестве дочерних.

Когда я перехожу от одного к другому, старый skscene не освобождается.

Я хочу, чтобы он освободился, как будто его там никогда не было.

Пример:

Когда я впервые загружаю приложение, видно только 1 skscene (скажем, занимает память 100 МБ), затем я перехожу к другому (еще 100 Мб), а затем третьему (300 мб памяти).

В итоге я получаю 300-мегабайтную память, и я хочу иметь 100 в любое время.

Как я могу это достичь?

My view controller: 

//
//  ViewController.m
//  Paddle Jumper
//
//  Created by Chance Daniel on 1/18/14.
//  Copyright (c) 2014 Max Hudson. All rights reserved.
//

#import "Flurry.h"
#import "ViewController.h"
#import "startViewController.h"

@implementation ViewController{
    BOOL sceneSetUp;
}

- (void)viewWillLayoutSubviews
{
    if(!sceneSetUp){

        [super viewWillLayoutSubviews];

        // Configure the view.
        SKView * skView = (SKView *)self.view;
        //skView.showsFPS = YES;
        //skView.showsNodeCount = YES;

        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        if([[defaults objectForKey:@"firstTime"] intValue] != 1){
            [defaults setObject:[NSNumber numberWithInt:1] forKey:@"firstTime"];

            [defaults setObject:@"ggr" forKey:@"skinSelected"];
            [defaults setObject:[NSNumber numberWithInt:2] forKey:@"ggrOwned"];

            [defaults setObject:[NSNumber numberWithInt:5000] forKey:@"gona"];
            [defaults setObject:[NSNumber numberWithInt:1500] forKey:@"points"];
            [defaults setObject:[NSNumber numberWithInt:7] forKey:@"livesLeftValue"];
            [defaults setObject:[NSNumber numberWithInt:3] forKey:@"shieldsLeftValue"];
            [defaults setObject:[NSNumber numberWithInt:2] forKey:@"lvlTwoLeftValue"];
            [defaults setObject:[NSNumber numberWithInt:0] forKey:@"lvlThreeLeftValue"];
        }

        if(![defaults objectForKey:@"tut_game1"]){
            [defaults setObject:[NSNumber numberWithInt:1] forKey:@"tut_game1"];
            [defaults setObject:[NSNumber numberWithInt:1] forKey:@"tut_store"];
            [defaults setObject:[NSNumber numberWithInt:1] forKey:@"tut_daily"];
        }

        [defaults synchronize];

        // Create and configure the scene.
        SKScene * startScene = [StartViewController sceneWithSize:skView.bounds.size];
        startScene.scaleMode = SKSceneScaleModeAspectFill;

        // Present the scene.
        [skView presentScene:startScene];
        //[skView presentScene:scene];

        sceneSetUp = YES;
    }

}

-(void) switchScene{

}

- (BOOL)shouldAutorotate
{
    return YES;
}

- (NSUInteger)supportedInterfaceOrientations
{
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        return UIInterfaceOrientationMaskAllButUpsideDown;
    } else {
        return UIInterfaceOrientationMaskAll;
    }
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}

@end

An SKScene that won't release: //
//  SettingsSKScene.m
//  Paddle Jumper
//
//  Created by Max Hudson on 3/15/14.
//  Copyright (c) 2014 Max Hudson. All rights reserved.
//

#import "SettingsSKScene.h"
#import "gameViewController.h"
#import "StoreScene.h"

@interface SettingsSKScene (){
    SKSpriteNode *bg;
    SKSpriteNode *masterOverlay;
    SKSpriteNode *wbox;
    SKSpriteNode *gamecenter;
    SKSpriteNode *max;
    SKSpriteNode *chance;
    SKSpriteNode *bryce;
    SKSpriteNode *home;
    SKSpriteNode *play;

    SKSpriteNode *chance_link;
    SKSpriteNode *max_link;
    SKSpriteNode *bryce_link;

    SKSpriteNode *fbButton;
    SKSpriteNode *fbToggleYes;
    SKSpriteNode *fbToggleNo;

    SKLabelNode *story1;
    SKLabelNode *story2;
    SKLabelNode *story3;

    SKLabelNode *chance_name;
    SKLabelNode *max_name;
    SKLabelNode *bryce_name;

    SKLabelNode *chance_role;
    SKLabelNode *max_role;
    SKLabelNode *bryce_role;

    SKLabelNode *chance_handle;
    SKLabelNode *max_handle;
    SKLabelNode *bryce_handle;

    SKTexture *bg_texture;
    SKTexture *gamecenter_texture;
    SKTexture *wbox_texture;
    SKTexture *max_texture;
    SKTexture *chance_texture;
    SKTexture *bryce_texture;
    SKTexture *home_texture;
    SKTexture *play_texture;
    SKTexture *fb_texture;
    SKTexture *toggle_yes_texture;
    SKTexture *toggle_no_texture;
}

@end

@implementation SettingsSKScene

-(id) initWithSize:(CGSize)size{
    if(self = [super initWithSize:size]){
        masterOverlay = [SKSpriteNode spriteNodeWithColor:[SKColor blackColor] size:self.frame.size];
        masterOverlay.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
        [self addChild:masterOverlay];

        bg_texture = [SKTexture textureWithImageNamed:@"settings_bg"];
        wbox_texture = [SKTexture textureWithImageNamed:@"white_rect_bg"];
        gamecenter_texture = [SKTexture textureWithImageNamed:@"gc"];
        max_texture = [SKTexture textureWithImageNamed:@"max"];
        chance_texture = [SKTexture textureWithImageNamed:@"chance"];
        bryce_texture = [SKTexture textureWithImageNamed:@"bryce"];
        home_texture = [SKTexture textureWithImageNamed:@"home_light"];
        play_texture = [SKTexture textureWithImageNamed:@"play_light"];
        fb_texture = [SKTexture textureWithImageNamed:@"fb_light"];
        toggle_yes_texture = [SKTexture textureWithImageNamed:@"toggle_yes"];
        toggle_no_texture = [SKTexture textureWithImageNamed:@"toggle_no"];

        NSArray *to_preload = @[bg_texture, wbox_texture, gamecenter_texture, max_texture, chance_texture, bryce_texture, home_texture, play_texture, fb_texture, toggle_yes_texture, toggle_no_texture];

        [SKTexture preloadTextures:to_preload withCompletionHandler:^{
            [self fadeRemove:masterOverlay];
            [self initialize];
        }];
    }
    return self;
}

-(void) fadeRemove: (SKNode *) node{
    double duration = arc4random() % 10;
    duration *= 0.05;
    SKAction *fadeOut = [SKAction fadeOutWithDuration:0.1+duration];
    SKAction *remove = [SKAction runBlock:^{
        [node removeFromParent];
    }];

    [node runAction:[SKAction sequence:@[fadeOut, remove]]];
}

-(void) initialize{
    bg = [SKSpriteNode spriteNodeWithTexture:bg_texture];
    bg.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
    bg.zPosition = 0;

    wbox = [SKSpriteNode spriteNodeWithTexture:wbox_texture];
    wbox.position = CGPointMake(self.frame.size.width/2, 70);
    wbox.alpha = 0.8;
    wbox.zPosition = 2;

    gamecenter = [SKSpriteNode spriteNodeWithTexture:gamecenter_texture];
    gamecenter.position = CGPointMake(self.frame.size.width/2 + 100, self.frame.size.height/2-2);
    gamecenter.name = @"gc";
    gamecenter.zPosition = 2;

    fbButton = [SKSpriteNode spriteNodeWithTexture:fb_texture];
    fbButton.size = CGSizeMake(36, 36);
    fbButton.position = CGPointMake(self.frame.size.width/2 - 115, self.frame.size.height/2-2);
    fbButton.name = @"fb";
    fbButton.zPosition = 2;

    fbToggleNo = [SKSpriteNode spriteNodeWithTexture:toggle_no_texture];
    fbToggleNo.position = CGPointMake(self.frame.size.width/2 - 50, self.frame.size.height/2-2);
    fbToggleNo.size = CGSizeMake(fbToggleNo.size.width/3, fbToggleNo.size.height/3);
    fbToggleNo.name = @"fbno";
    fbToggleNo.zPosition = 2;

    fbToggleYes = [SKSpriteNode spriteNodeWithTexture:toggle_yes_texture];
    fbToggleYes.position = CGPointMake(self.frame.size.width/2 - 70, self.frame.size.height/2-2);
    fbToggleYes.name = @"fbyes";
    fbToggleYes.zPosition = 2;

    int hpBuffer = 40;
    int hpZ = 2;

    home = [SKSpriteNode spriteNodeWithTexture:home_texture];
    home.position = CGPointMake(hpBuffer, self.frame.size.height - hpBuffer);
    home.zPosition = hpZ;
    home.name = @"home";

    play = [SKSpriteNode spriteNodeWithTexture:play_texture];
    play.position = CGPointMake(self.frame.size.width - hpBuffer, self.frame.size.height - hpBuffer);
    play.zPosition = hpZ;
    play.name = @"play";

    [self addChild:bg];
    [self addChild:wbox];
    [self addChild:gamecenter];
    [self addChild:home];
    [self addChild:play];

    [self addChild:fbButton];
    [self addChild:fbToggleNo];

    [self addCredits];
    [self addStory];
}

-(void) addCredits{
    /* images */

    int cmbZ = wbox.zPosition + 1;
    int cmbY = wbox.position.y;
    int cmbXUnit = 132;
    int cmbX = self.frame.size.width/2 - (3*cmbXUnit)/2 + 20;
    int cmbWidth = 40;

    chance  = [SKSpriteNode spriteNodeWithTexture:chance_texture];
    max = [SKSpriteNode spriteNodeWithTexture:max_texture];
    bryce  = [SKSpriteNode spriteNodeWithTexture:bryce_texture];

    chance.zPosition = cmbZ;
    max.zPosition = cmbZ;
    bryce.zPosition = cmbZ;

    chance.position = CGPointMake(cmbX+cmbXUnit*0, cmbY+3);
    max.position = CGPointMake(cmbX+cmbXUnit*1 + 10, cmbY);
    bryce.position = CGPointMake(cmbX+cmbXUnit*2 + 10, cmbY+5);

    chance.size = CGSizeMake(cmbWidth, (chance.size.height/chance.size.width)*cmbWidth);
    max.size = CGSizeMake(cmbWidth, (max.size.height/max.size.width)*cmbWidth);
    bryce.size = CGSizeMake(cmbWidth, (bryce.size.height/bryce.size.width)*cmbWidth);

    [self addChild:chance];
    [self addChild:max];
    [self addChild:bryce];

    /* names */

    int cmb_nameXUnit = 30;
    int cmb_nameY = wbox.position.y - 10;
    int cmb_nameFontSize = 18;

    chance_name = [SKLabelNode labelNodeWithFontNamed:@"BebasNeue"];
    max_name = [SKLabelNode labelNodeWithFontNamed:@"BebasNeue"];
    bryce_name = [SKLabelNode labelNodeWithFontNamed:@"BebasNeue"];

    chance_name.text = @"Chance Daniel";
    max_name.text = @"Max Hudson";
    bryce_name.text = @"Bryce Daniel";

    chance_name.fontColor = [SKColor blackColor];
    max_name.fontColor = [SKColor blackColor];
    bryce_name.fontColor = [SKColor blackColor];

    chance_name.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
    max_name.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
    bryce_name.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;

    chance_name.position = CGPointMake(chance.position.x + cmb_nameXUnit, cmb_nameY);
    max_name.position = CGPointMake(max.position.x + cmb_nameXUnit, cmb_nameY);
    bryce_name.position = CGPointMake(bryce.position.x + cmb_nameXUnit, cmb_nameY);

    chance_name.fontSize = cmb_nameFontSize;
    max_name.fontSize = cmb_nameFontSize;
    bryce_name.fontSize = cmb_nameFontSize;

    chance_name.zPosition = cmbZ;
    max_name.zPosition = cmbZ;
    bryce_name.zPosition = cmbZ;

    [self addChild:chance_name];
    [self addChild:max_name];
    [self addChild:bryce_name];

    /* roles */

    int cmb_roleY = wbox.position.y - 25;
    int cmb_roleFontSize = 11;

    chance_role = [SKLabelNode labelNodeWithFontNamed:@"BebasNeue"];
    max_role = [SKLabelNode labelNodeWithFontNamed:@"BebasNeue"];
    bryce_role = [SKLabelNode labelNodeWithFontNamed:@"BebasNeue"];

    chance_role.text = @"Programmer";
    max_role.text = @"Programmer";
    bryce_role.text = @"Graphic Designer";

    chance_role.fontColor = [SKColor darkGrayColor];
    max_role.fontColor = [SKColor darkGrayColor];
    bryce_role.fontColor = [SKColor darkGrayColor];

    chance_role.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
    max_role.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
    bryce_role.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;

    chance_role.position = CGPointMake(chance.position.x + cmb_nameXUnit + 19, cmb_roleY);
    max_role.position = CGPointMake(max.position.x + cmb_nameXUnit + 12, cmb_roleY);
    bryce_role.position = CGPointMake(bryce.position.x + cmb_nameXUnit + 6, cmb_roleY);

    chance_role.fontSize = cmb_roleFontSize;
    max_role.fontSize = cmb_roleFontSize;
    bryce_role.fontSize = cmb_roleFontSize;

    chance_role.zPosition = cmbZ;
    max_role.zPosition = cmbZ;
    bryce_role.zPosition = cmbZ;

    [self addChild:chance_role];
    [self addChild:max_role];
    [self addChild:bryce_role];

    /* twitter handles */

    int cmb_handY = wbox.position.y - 40;
    int cmb_handFontSize = 10;

    chance_handle = [SKLabelNode labelNodeWithFontNamed:@"BebasNeue"];
    max_handle = [SKLabelNode labelNodeWithFontNamed:@"BebasNeue"];
    bryce_handle = [SKLabelNode labelNodeWithFontNamed:@"BebasNeue"];

    chance_handle.text = @"@ChanceOfThat";
    max_handle.text = @"@max_hud";
    bryce_handle.text = @"@BryceOfLife";

    chance_handle.fontColor = [SKColor darkGrayColor];
    max_handle.fontColor = [SKColor darkGrayColor];
    bryce_handle.fontColor = [SKColor darkGrayColor];

    chance_handle.position = CGPointMake(chance.position.x, cmb_handY);
    max_handle.position = CGPointMake(max.position.x, cmb_handY);
    bryce_handle.position = CGPointMake(bryce.position.x, cmb_handY);

    chance_handle.fontSize = cmb_handFontSize;
    max_handle.fontSize = cmb_handFontSize;
    bryce_handle.fontSize = cmb_handFontSize;

    chance_handle.zPosition = cmbZ;
    max_handle.zPosition = cmbZ;
    bryce_handle.zPosition = cmbZ;

    [self addChild:chance_handle];
    [self addChild:max_handle];
    [self addChild:bryce_handle];

    /* touchzones */

    CGSize cmdL_size = CGSizeMake(120, 70);
    SKColor *cmdL_color = [SKColor clearColor];
    int cmdLZ = 3;
    int cmdLX = cmbX + 40;

    chance_link = [SKSpriteNode spriteNodeWithColor:cmdL_color size:cmdL_size];
    max_link = [SKSpriteNode spriteNodeWithColor:cmdL_color size:cmdL_size];
    bryce_link = [SKSpriteNode spriteNodeWithColor:cmdL_color size:cmdL_size];

    chance_link.position = CGPointMake(cmdLX+cmbXUnit*0, cmbY);
    max_link.position = CGPointMake(cmdLX+cmbXUnit*1 + 10, cmbY);
    bryce_link.position = CGPointMake(cmdLX+cmbXUnit*2 + 10, cmbY);

    chance_link.zPosition = cmdLZ;
    max_link.zPosition = cmdLZ;
    bryce_link.zPosition = cmdLZ;

    chance_link.name = @"c_handle";
    max_link.name = @"m_handle";
    bryce_link.name = @"b_handle";

    [self addChild:chance_link];
    [self addChild:max_link];
    [self addChild:bryce_link];
}

-(void) addStory{

    int stX = self.frame.size.width/2;
    int stZ = 2;
    SKColor *stColor = [SKColor whiteColor];

    story1 = [SKLabelNode labelNodeWithFontNamed:@"BebasNeue"];
    story2 = [SKLabelNode labelNodeWithFontNamed:@"BebasNeue"];
    story3 = [SKLabelNode labelNodeWithFontNamed:@"BebasNeue"];

    story1.text = @"Gon";
    story2.text = @"Help Gon-Gon and his friends get";
    story3.text = @"Back to the time they came from!";

    story1.fontColor = stColor;
    story2.fontColor = stColor;
    story3.fontColor = stColor;

    story1.zPosition = stZ;
    story2.zPosition = stZ;
    story3.zPosition = stZ;

    story1.position = CGPointMake(stX, self.frame.size.height - 55);
    story1.fontSize = 50;

    story2.position = CGPointMake(stX, self.frame.size.height - 95);
    story2.fontSize = 30;

    story3.position = CGPointMake(stX, self.frame.size.height - 120);
    story3.fontSize = 20;

    [self addChild:story1];
    [self addChild:story2];
    [self addChild:story3];
}

-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touchUI = [touches anyObject];
    CGPoint touchPoint = [touchUI locationInNode:self];
    SKNode *touchNode = [self nodeAtPoint:touchPoint];

    if([touchNode.name isEqualToString:@"home"]){
        StartViewController *svc = [[StartViewController alloc] initWithSize:self.size];
        SKTransition *fade = [SKTransition fadeWithColor :[SKColor blackColor] duration:0.4];
        [self.view presentScene:svc transition:fade];
    }

    if([touchNode.name isEqualToString:@"play"]){
        gameViewController *gvc = [[gameViewController alloc] initWithSize:self.size];
        SKTransition *fade = [SKTransition fadeWithColor :[SKColor blackColor] duration:0.4];
        [self.view presentScene:gvc transition:fade];
    }

    if([touchNode.name isEqualToString:@"gc"]){
        NSDictionary * dict = [[NSDictionary alloc]initWithObjectsAndKeys:[NSNumber numberWithBool:1], @"showGC", nil];

        [[NSNotificationCenter defaultCenter]postNotificationName:@"kNotificationUpdateBannerView" object:self userInfo:dict];
    }

    if([touchNode.name isEqualToString:@"c_handle"]){
        NSURL *urlApp = [NSURL URLWithString: [NSString stringWithFormat:@"%@", @"twitter:///user?screen_name=ChanceOfThat"]];
        int worked = [[UIApplication sharedApplication] openURL:urlApp];
        if(!worked){
            NSURL *urlApp = [NSURL URLWithString: [NSString stringWithFormat:@"%@", @"https://twitter.com/#!/ChanceOfThat"]];
            [[UIApplication sharedApplication] openURL:urlApp];
        }
    }

    if([touchNode.name isEqualToString:@"m_handle"]){
        NSURL *urlApp = [NSURL URLWithString: [NSString stringWithFormat:@"%@", @"twitter:///user?screen_name=max_hud"]];
        int worked = [[UIApplication sharedApplication] openURL:urlApp];
        if(!worked){
            NSURL *urlApp = [NSURL URLWithString: [NSString stringWithFormat:@"%@", @"https://twitter.com/#!/max_hud"]];
            [[UIApplication sharedApplication] openURL:urlApp];
        }
    }

    if([touchNode.name isEqualToString:@"b_handle"]){
        NSURL *urlApp = [NSURL URLWithString: [NSString stringWithFormat:@"%@", @"twitter:///user?screen_name=BryceOfLife"]];
        int worked = [[UIApplication sharedApplication] openURL:urlApp];
        if(!worked){
            NSURL *urlApp = [NSURL URLWithString: [NSString stringWithFormat:@"%@", @"https://twitter.com/#!/BryceOfLife"]];
            [[UIApplication sharedApplication] openURL:urlApp];
        }
    }
}

@end

Ответ 1

Аналогичная проблема возникла у человека, который спросил об этом question.

Когда его спросили, может ли он решить это, они сказали:

Да, я сделал, я ничего не мог с этим сделать со сцены или Sprite Kit, в этом отношении мне просто нужно было снять сцену и представление, содержащее его полностью из родительского представления, разрезать все его привязки к другим частям системы, чтобы память была также освобождается.

Вы должны использовать отдельные виды для каждой сцены и перехода между этими видами. Вы можете выполнить следующие действия, чтобы сделать его естественным:

1 - В тот момент, когда вы хотите перейти от одной сцены к другой, сделайте снимок сцены, используя следующий код:

UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, scale);
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

Затем добавьте это изображение в качестве поднабора SKView текущей сцены и удалите сцену.

2 - Инициализация новой сцены на другом представлении и переход между двумя видами с использованием анимации UIView.

3 - Удалите из него вид сверху.

Ответ 2

Я только начинаю с SpriteKit в Swift, и у меня была такая же проблема: моя сцена "intro" воспроизводила музыку, которую я хотел остановить после перехода к сцене главного меню, и я решил, что deinit будет хорошим местом чтобы включить вызов AVAudioPlayer.stop, но деинициализатор никогда не вызывался.

После некоторого осмотра я узнал, что это может быть из-за сильных ссылок на сцену, поэтому я изменил

let s = StartupScene(size: skView.bounds.size)
skView.presentScene(s)

to

skView.presentScene(StartupScene(size: skView.bounds.size))

в моем GameViewController: UIViewController и

let s = MainMenuScene(size: self.size)
let t = SKTransition.crossFadeWithDuration(1)
self.scene.view.presentScene(s, transition: t)

to

self.scene.view.presentScene(MainMenuScene(size: self.size), transition: SKTransition.crossFadeWithDuration(1))

в сцене, которую я хотел освободить, и это сработало! После завершения перехода был вызван метод deinit.

Надеюсь, кто-то более опытный может отредактировать этот ответ, чтобы лучше объяснить, что именно произошло здесь.

Ответ 3

EDIT: моя предыдущая идея о NSTimer была неактуальной

Чтобы убедиться, что это проблема, выделенная для этой сцены, переопределите методы dealloc всех сцен, которые у вас могут быть (включая этот) следующим образом:

 -(void)dealloc {
  NSLog(@"Dealloc <scene name>");
 }

Посмотрите на свои другие смены сцены, посмотрите, правильно ли они освободятся. Найдите различия между этими сценами. Это поможет вам понять, является ли это изолированной проблемой или большой проблемой. После исправления проблемы обязательно закомментируйте или удалите метод dealloc, поскольку он переопределяет тот, который фактически освобождает память. Надеюсь, это поможет!

Ответ 4

Нет ничего плохого в SKScene или SKView, пока я вижу. Удостоверьтесь, что экземпляр сцены не сильно ссылается нигде, особенно внутри блока. Блоки очень вероятны для игнорирования.

Подробнее о слабой ссылке внутри блока: fooobar.com/info/175769/...

Насколько я вижу, у вас есть блок, сильно ссылающийся на экземпляр сцены:

[SKTexture preloadTextures:to_preload withCompletionHandler:^{
    [self fadeRemove:masterOverlay];
    [self initialize];
}];