iOS 7 Sprite Kit освобождает память

Я создаю игру для iOS, предназначенную для новых iOS 7 и Sprite Kit, используя узлы-эмиттеры и физику для улучшения игрового процесса. При разработке приложения я столкнулся с серьезной проблемой: вы создаете свои сцены, узлы, эффекты, но когда вы закончите и должны вернуться на главный экран, как вы освобождаете всю память, выделенную этими ресурсами?

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

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

- (void) dealloc
{
    if (scene != nil)
    {
        [scene setPaused:YES];

        [scene removeAllActions];
        [scene removeAllChildren];

        scene = nil;

        [((SKView *)sceneView) presentScene:nil];

        sceneView = nil;
    }
}
  • sceneView - это UIView, который является контейнером сцены
  • сцена является расширением класса SKScene, создавая все объекты SKSpriteNode

Я был бы очень признателен за любую помощь в этом вопросе.

7 ответов

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

Базовая память, выделенная с помощью + textureWithImageNamed: может или не может (обычно нет) высвобождаться при переключении на новую SKScene. Вы не можете полагаться на это. iOS освобождает кешируемую память с помощью + textureWithImageNamed: или + imageNamed: когда считает нужным, например, когда обнаруживает состояние нехватки памяти.

Если вы хотите освободить память, как только закончите с текстурами, вы должны избегать использования +textureWithImageNamed:/+imageNamed:. Альтернативой для создания SKTextures является: сначала создать UIImages с +imageWithContentsOfFile:, а затем создать SKTextures из результирующих объектов UIImage, вызвав SKTexture/+textureWithImage:(UIImage*).

Я не знаю, поможет ли это здесь.

Весь этот код является излишним. При условии, что в вашем коде нет утечек памяти или циклов сохранения, после выпуска представления набора Sprite все будет очищено из памяти.

Под капотом Sprite Kit используется механизм кэширования, но у нас нет способа его контролировать, и нам не нужно это делать, если он правильно реализован (что можно с уверенностью предположить).

Если это не то, что вы видите в приборах, проверьте, нет ли циклов, утечек. Убедитесь, что освобождение сцены и вида вызывается. Убедитесь, что в других объектах (особенно синглетах и ​​глобальных переменных) не осталось сильных ссылок на представление, сцену или другие узлы.

После борьбы с этим в течение нескольких дней ключ был фактически: [sceneView presentScene:nil]; Или для Swift: sceneView.presentScene(ноль)

что можно сделать в viewDidDisappear. Без этого ваш взгляд будет цепляться за сцену для дорогой жизни, даже после увольнения, и продолжать жевать память.

Свифт 3:

На своем личном опыте я решил с помощью инструментов Xcode, в первую очередь с помощью монитора активности, который сразу же показал мне огромное увеличение памяти, чем с распределением и утечками.

Но есть и полезный способ, отладочная консоль с

deinit {
       print("\n THE SCENE \(type(of:self)) WAS REMOVED FROM MEMORY (DEINIT) \n")
}

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

Вы никогда не имеете сильных ссылок на scene или же parent в ваших классах, если у вас есть кто-то, вы должны преобразовать его в слабый, например:

weak var parentScene:SKScene?

То же самое для протокола, вы можете объявить его слабым, как в этом примере, используя свойство class:

protocol ResumeBtnSelectorDelegate: class {
    func didPressResumeBtn(resumeBtn:SKSpriteNode)
}

weak var resumeBtnDelegate:ResumeBtnSelectorDelegate?

ARC выполняет всю необходимую нам работу, но, если вы думаете, что забыли правильно написать какое-то свойство (инициализация, блоки...), я использовал также некоторые функции, подобные этой, для моих фаз отладки:

func cleanScene() {
    if let s = self.view?.scene {
        NotificationCenter.default.removeObserver(self)
        self.children
            .forEach {
                $0.removeAllActions()
                $0.removeAllChildren()
                $0.removeFromParent()
        }
        s.removeAllActions()
        s.removeAllChildren()
        s.removeFromParent()
    }
}

override func willMove(from view: SKView) {
    cleanScene()
    self.removeAllActions()
    self.removeAllChildren()
}

У меня была похожая проблема, как у вас @user2857148. Я хотел бы представить ВК с:

[self presentViewController:myViewController animated:YES completion:nil];

в @implementation myViewController Я имел:

- (void)viewDidLayoutSubviews
{
    // Configure the view.
    SKView * skView = (SKView *)self.view;
    skView.showsFPS = YES;
    skView.showsNodeCount = YES;
    self.ballonMGScene = [[MBDBallonMiniGame alloc] initWithSize:skView.bounds.size andBallonImageNames:self.ballonObjectsArray];
    self.ballonMGScene.parentVC = self;
    self.ballonMGScene.scaleMode = SKSceneScaleModeAspectFill;
    self.ballonMGScene.physicsWorld.gravity = CGVectorMake(0, 0);
    // Present the scene.
    [skView presentScene:self.ballonMGScene];
} 

Проблема была в:

self.ballonMGScene.parentVC = self;

так как в:

@interface MBDBallonMiniGame : SKScene <SKPhysicsContactDelegate>

parentVC был объявлен с сильным:

@property (nonatomic,strong) WBMMiniGameVCTemplate *parentVC;

Решение 1:

и изменив его на:

@property (nonatomic,weak) WBMMiniGameVCTemplate *parentVC;

решил проблему для меня.

Пояснение: ссылка на parentVC (myViewController) который был UIViewController был сохранен где-то. Поскольку этот VC имел сильную ссылку на SKScene, он был сохранен вместе с ним. У меня даже был вывод консоли из этого SKScene, как будто он все еще был активен. Мой лучший вопрос о том, почему это случилось со мной, был то, что у меня есть самые сильные указатели.

Решение 2:

В моем myViewController под:

- (void)viewDidDisappear:(BOOL)animated

Я звонил:

self.ballonMGScene.parentVC = nil;

На выходе из текущего ВК (myViewController) Я установил указатель на ноль, удалив память и все вместе с ней.

Эти 2 решения работали для меня. Я проверил это с помощью отладчика. Потребление памяти увеличивалось и уменьшалось правильно.

Надеюсь, что это помогает в понимании проблемы и решения.

Попробуйте сохранить sceneView перед удалением сцены.

-(void)dealloc
{
[sceneView presentScene:nil];
[sceneView release];
[super dealloc];
}

Потребовалось несколько шагов, но я полностью решил свою проблему:

1) В My ViewControl я создал метод принудительного уничтожения всех дочерних элементов:

-(void)destroyAllSub:(SKNode*)node
{
    if(node == nil) return;
    if(![node isKindOfClass:[SKNode class]]) return;

    [node removeAllActions];
    for (SKNode *subNode in node.children) {
        [self destroyAllSub:subNode];
    }
    [node removeAllChildren];
}

2) Поскольку я создал сильный протокол в моей сцене и ссылался на него в своем ViewControl, и моя сцена также была сильной, я уничтожил все ссылки следующим образом:

[self.mainScene.view presentScene:nil]; //mainScene: the name of the Scene pointer
self.mainScene.myProt = nil; //myProt: The name of the strong protocol

@autoreleasepool {
    [self destroyAllSub:self.mainScene];
    self.mainScene = nil;
}
Другие вопросы по тегам