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;
}