iOS 7 Sprite Kit 如何释放内存

21

我正在开发一个面向新iOS 7和Sprite Kit的iOS游戏,使用发射器节点和物理效果来增强游戏玩法。在开发应用程序时,我遇到了一个严重的问题:当你创建场景、节点和效果之后,当你完成并需要返回主屏幕时,如何释放这些资源分配的内存?

理想情况下,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对象

我非常感谢任何有关此事的帮助。


我也遇到了同样的问题,即使我调用了dismissViewControllerAnimated,我的场景仍然在运行,你解决了吗? - David Lawson
1
是的,我做了,从场景或Sprite Kit方面来说,我无法对此做任何事情,我只需要完全从父视图中删除场景和包含它的视图,切断它与系统其他部分的所有联系,以便内存也能被释放。 - Lehel Medves
7个回答

20

我在使用Sprite Kit时遇到了很多内存问题,于是我使用技术支持票来获取信息,这可能与此处有关。我询问是否启动新的SKScene会完全释放前一个场景使用的所有内存。我发现了这个:

+textureWithImageNamed:分配的底层内存可能(通常不会)在切换到新的SKScene时被释放。 你不能依靠它。 当iOS检测到低内存条件时,它释放由+textureWithImageNamed:或+imageNamed:缓存的内存。

如果你希望在完成纹理后立即释放内存,你必须避免使用+textureWithImageNamed:/+imageNamed:。创建SKTextures的替代方法是:首先使用+imageWithContentsOfFile:创建UIImages,然后通过调用SKTexture / +textureWithImage:(UIImage *)从生成的UIImage对象创建SKTextures。

我不知道这是否对这里有所帮助。


9

所有这些代码都是多余的。只要你的代码中没有内存泄漏或保留周期,一旦释放Sprite Kit视图,所有内容都将从内存中清除。

在底层,Sprite Kit使用缓存机制,但我们无法控制它,如果它被正确实现(可以安全地假设),我们也不需要控制它。

如果在Instruments中看到的不是这个情况,请检查是否存在保留周期和泄漏。验证场景和视图的dealloc是否被调用。确保没有强引用残留在其他对象(特别是单例和全局变量)中的视图、场景或其他节点。


感谢您的快速回答!确实,没有泄漏存在(除了物理模块产生的一对64字节泄漏),场景或SKView对象中没有保留循环或强引用,但是正常行为应该是,如果我使用180MB的分配内存创建场景,在使用场景期间增加到230MB,退出后,它应该回落到非常接近180MB的值 - 当然,这是没有任何缓存的情况下。您认为这种行为是否可以通过Sprite Kit的缓存来证明? - Lehel Medves
2
可能需要明确的测试来验证。也许SK会将缓存的纹理保留以备后续重用,并仅在内存警告时释放它们。在模拟器中尝试发送内存警告,看看内存是否会下降。 - CodeSmile
4
有趣的想法,但不,即使在内存警告后也不会掉落。 - Lehel Medves

9

经过数天的斗争,关键在于: [sceneView presentScene:nil]; 或者对于Swift来说: sceneView.presentScene(nil)

这可以在viewDidDisappear中完成。如果没有这个操作,你的视图会紧紧地抓住场景,即使被解散了,也会继续消耗内存。


1
我已经尝试过这个方法,但是即使在设置了sceneView.presentScene(nil)之后呈现下一个场景,屏幕仍然会变成空白。有什么解决办法吗? - J.Treutlein

5

Swift 3:

根据我的个人经验,我使用Xcode工具解决了一些问题,主要是通过活动监视器来查看内存的增长情况,然后使用分配和泄漏调试工具。

但还有一种有用的方法,就是使用调试控制台。

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

这是另一种帮助,以查看每次想要删除场景时是否调用了deinit

在您的类中,不要对sceneparent进行强引用,如果您有任何一个,必须将其转换为弱引用,例如:

weak var parentScene:SKScene?

同样的,对于协议(protocol),你可以像这个例子一样使用属性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()
}

1

我有一个类似于你@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;

since in :

@interface MBDBallonMiniGame : SKScene <SKPhysicsContactDelegate>

parentVC被声明为strong

@property (nonatomic,strong) WBMMiniGameVCTemplate *parentVC;

解答1:

解决方案1:

将其更改为:

@property (nonatomic,weak) WBMMiniGameVCTemplate *parentVC;

帮我解决了问题。

说明: 对于存储在某处的父级视图控制器(myViewController),它是一个UIViewController,已经被引用。由于这个VC对SKScene有强引用,所以它与之一起被存储。我甚至从这个SKScene中得到了控制台输出,就像它仍然活跃着一样。我猜测发生这种情况的原因是我有最多的强指针。

解决方案2:

在我的myViewController下:

- (void)viewDidDisappear:(BOOL)animated

我打电话:
self.ballonMGScene.parentVC = nil;

当离开当前视图控制器(myViewController)时,我将指针设置为nil,删除内存以及所有相关内容。

这两种解决方案对我有效。我使用调试器进行了测试,内存消耗正确地上升和下降。

希望这有助于理解问题和解决方案。


0

在移除场景之前尝试保留sceneView。

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

每次我回到舞台时,性能都会下降。我使用了苹果SpriteKit的默认模板。在Swift中,我只是添加了这个函数,性能似乎恢复正常了。override func viewWillDisappear(animated: Bool) { if let skView = self.view as? SKView { skView.presentScene(nil) } } - endavid

0

经过一些步骤,但我终于彻底解决了我的问题:

1)在我的 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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接