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
Я бы очень признателен за любую помощь в этом вопросе.
Ответы
Ответ 1
У меня было много проблем с памятью в Sprite Kit, и я использовал билет технической поддержки, чтобы получить информацию, и это может касаться здесь. Я спрашивал, будет ли запуск новой SKScene полностью освободить всю память предыдущей. Я узнал об этом:
Базовая память, выделенная + textureWithImageNamed: может быть или не быть (как правило, не удалена) при переключении на новый SKScene. Вы не можете полагаться на это. iOS освобождает память, кэшированную + textureWithImageNamed: или + imageNamed: когда она сочтет нужным, например, когда она обнаруживает условие с низкой памятью.
Если вы хотите, чтобы память была выпущена, как только вы сделали с текстурами, вы должны избегать использования + textureWithImageNamed:/+ imageNamed:. Альтернативой созданию SKTextures является: сначала создать UIImages с + imageWithContentsOfFile:, а затем создать SKTextures из результирующих объектов UIImage, вызвав SKTexture/+ textureWithImage: (UIImage *).
Я не знаю, поможет ли это здесь.
Ответ 2
Весь этот код лишний. Если у вас нет утечек памяти или удержите циклы в вашем коде, как только вы освободите представление Sprite Kit, все будет очищено из памяти.
Под капотом Sprite Kit использует механизм кэширования, но мы не можем его контролировать, и нам не нужно, если он будет правильно реализован (что можно с уверенностью предположить).
Если это не то, что вы видите в "Инструменты", проверьте наличие циклов, утечек. Убедитесь, что dealloc сцены и просмотра вызваны. Убедитесь, что никакие ссылки на просмотр, сцену или другие узлы не остаются в других объектах (в частности, одиночных и глобальных переменных).
Ответ 3
После борьбы с ним в течение пары дней ключ был фактически: [sceneView presentScene: nil];
Или для Swift: sceneView.presentScene(ноль)
который можно сделать в viewDidDisappear. Без этого ваше мнение будет зависеть от сцены для дорогой жизни, даже после того, как ее уволят, и продолжать пережевывать память.
Ответ 4
Swift 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 выполнить всю необходимую нам работу, но если вы считаете, что забыли написать правильное свойство (initiliazation, blocks..), я использовал также некоторые функции, такие как для фаз отладки:
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()
}
Ответ 5
У меня была аналогичная проблема, как вы @user2857148.
Я бы представил VC с:
[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;
решил проблему для меня.
Объяснение:
Ссылка на родительский VC (myViewController
), который был UIViewController
, был где-то сохранен. Поскольку этот VC имел сильную ссылку на SKScene, он был сохранен вместе с ним. У меня даже был консольный вывод из этой SKScene, как будто он все еще активен. Моя лучшая проблема в том, почему это случилось со мной, было то, что у меня были самые сильные указатели.
Решение 2:
В моем myViewController
в разделе:
- (void)viewDidDisappear:(BOOL)animated
Я позвонил:
self.ballonMGScene.parentVC = nil;
При выходе из текущего VC (myViewController
) я устанавливаю указатель на nil, удаляя память и все вместе с ней.
Эти 2 решения работали для меня. Я тестировал его с помощью отладчика. Потребление памяти пошло вверх и вниз правильно.
Надеюсь, это поможет понять проблему и решения.
Ответ 6
Попробуйте сохранить сцену перед удалением сцены.
-(void)dealloc
{
[sceneView presentScene:nil];
[sceneView release];
[super dealloc];
}
Ответ 7
Сделали несколько шагов, но я полностью решил свою проблему:
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;
}