即使调用了 removeAllChildren,SpriteKit 中的内存也会增加

Posted

技术标签:

【中文标题】即使调用了 removeAllChildren,SpriteKit 中的内存也会增加【英文标题】:Memory Increase in SpriteKit even though removeAllChildren called 【发布时间】:2015-12-02 20:46:43 【问题描述】:

我已经尝试修复 SpriteKit 中内存大幅增加的问题已经好几个星期了,但没有找到解决方案。我已经尝试了几乎所有在网上遇到的用于管理 SK 内存的方法,但仍然遇到问题。这是我的情况:

我首先展示 SKScene“HomeScreen”,然后在我的视图控制器中使用 UIButton 更改页面场景,如下所示:

 func nextPage(sender: UIButton) 

    pageNumber = pageNumber + 1

    let switchPage = SKTransition.crossFadeWithDuration(3.0)
    switchPage.pausesOutgoingScene = false

    switch pageNumber 
    case 1: self.skView.presentScene(Page01(size: (self.view?.bounds.size)!), transition: switchPage)
    case 2: self.skView.presentScene(Page02(size: (self.view?.bounds.size)!), transition: switchPage)
    case 3: self.skView.presentScene(Page03(size: (self.view?.bounds.size)!), transition: switchPage)
    case 4: self.skView.presentScene(Page04(size: (self.view?.bounds.size)!), transition: switchPage)
    case 5: self.skView.presentScene(Page05(size: (self.view?.bounds.size)!), transition: switchPage)
    case 6: self.skView.presentScene(Page06(size: (self.view?.bounds.size)!), transition: switchPage)
    case 7: self.skView.presentScene(Page07(size: (self.view?.bounds.size)!), transition: switchPage)
    case 8: self.skView.presentScene(Page08(size: (self.view?.bounds.size)!), transition: switchPage)
    case 9: self.skView.presentScene(Page09(size: (self.view?.bounds.size)!), transition: switchPage)
    case 10: self.skView.presentScene(Page10(size: (self.view?.bounds.size)!), transition: switchPage)
    case 11: self.skView.presentScene(Page11(size: (self.view?.bounds.size)!), transition: switchPage)
    default:
        break
    

这是我在 Xcode 中的内存图:

如您所见,我对 HomeScreen (HS) 的记忆从 38.1 开始,当我循环浏览所有场景并返回主屏幕时,它在 112 结束。我还单独加载了每个页面并记录了内存以确认增加。

在每个 SKScene 中,我加载各种对象,然后在 willMoveFromView 中删除它们:

    override func willMoveFromView(view: SKView) 

    //Remove the Scroller Components
    self.view?.removeGestureRecognizer(self.page01Drops.panGestureRecognizer)
    self.page01Drops = nil

    //Remove Environment
    self.randomObjectFromScene.texture = nil

    self.anotherRandomObject.removeFromParent

    self.removeAllActions()
    self.removeAllChildren()

这里有更多信息: - 场景更改后不会调用 Deinit。

我在使用 Instruments 测试应用时没有泄漏。

首先使用 stringByAppendingPathComponent 将所有 SKTexture 作为 UIImage 加载。

所有 willMoveFromView 方法都包含 removeAllActions 和 removeAllChildren。

有人知道为什么我的记忆力会增加吗?

更新更多示例代码

这是我在 viewDidLoad 中加载我的 skView 的方式:

    skView = view as! SKView
    skView.showsFPS = true
    skView.showsNodeCount = true
    skView.ignoresSiblingOrder = false
    skView.presentScene(homeScreen(size: skView.bounds.size))

这里有一点来自我的 Page01 SKScene:

class Page01: SKScene 
    //Preload Textures
    let aaronHead01Texture = SKTexture(image: UIImage(contentsOfFile:NSBundle.mainBundle().resourcePath!.stringByAppendingPathComponent("P01_head01.png"))!)
    let aaronHead02Texture = SKTexture(image: UIImage(contentsOfFile:NSBundle.mainBundle().resourcePath!.stringByAppendingPathComponent("P01_head02.png"))!)
    //more textures for body parts of character that will be changed

    var aaronHead: SKSpriteNode!
    //More SKSpriteNodes for other body parts

    override init(size: CGSize)
        super.init(size: size)

        self.aaronHead = SKSpriteNode(texture: self.aaronHead01Texture)
        self.aaronHead.anchorPoint = CGPoint(x: 0.5, y: 0.1)
        self.aaronHead.position = CGPoint(x: 10, y: 284)
        self.aaronHead.zPosition = 1.0
        self.aaronBody.addChild(aaronHead)

    

【问题讨论】:

正如 appzYourLife 已经指出的那样,您可能有一个强大的参考周期。搜索诸如 unowned self、weak self 等术语。还可以查看文档(作为最佳参考)developer.apple.com/library/prerelease/ios/documentation/Swift/… 如果您愿意,您可以发布一些场景的属性以及视图控制器的属性,我可以看看是否能找到任何明显的保留周期。你说 deinit 没有被调用的事实证明你的代码中肯定有一个保留周期。您可能还想制作一个使用 deinit 工作的示例项目,然后慢慢添加您的代码,直到 deinit 停止工作。这更像是一种反复试验的方法,但它会帮助您找到问题。 感谢 Epic Byte,我刚刚添加了一些代码。在我的每个 SKScene 中,我为我的每个角色的身体部位创建了大约 12 个左右的 SKSpriteNode。 我发现调用一个运行 self.aaronHead.texture = self.aaronHead02Texture 的块被保留了。 请参阅developer.apple.com/library/ios/documentation/Swift/Conceptual/…,特别是阅读我认为可能导致您的问题的闭包的强参考周期部分。 【参考方案1】:

你已经发现了问题:

场景改变后没有调用 Deinit。

如果scene 确实改变了旧场景的deinit 没有被调用,那就是问题所在。说明旧场景还在记忆中。

这可能是因为您有一个强大的保留周期。这意味着您的场景的一个孩子(或孩子的孩子......)对场景本身有一个强引用

您必须找到该引用并使用 weak 关键字声明它。

【讨论】:

感谢 appzYourLife 的提示。这是否意味着需要将 SKNodes、SKSpriteNodes 或 SKLabelNodes 之类的东西设置为弱? @jwade502:实际上,当您向场景(或另一个节点)添加SKNode(或SKSpriteNodeSKLabelNode)时,您应该只写self.addChild(anotherNode)。 SpriteKit 将处理引用。您是否正在为节点创建属性以引用其他节点? 仅适用于纹理。我在 init 方法的顶部创建 SKTexture 属性,然后在需要在 SKSpriteNode 上设置纹理时使用这些属性。 我仍然相信你有很强的保留周期。我建议您将此deinit debugPrint("\(NSStringFromClass(self.dynamicType)).deinit") 添加到您定义的每个自定义节点中。然后运行你的游戏。当您更改场景时,场景中的每个节点 都应该被取消初始化。只需查找未释放的实例并找出未释放的实例。这意味着您对应该声明为弱的节点有强引用。 我在一个名为 SpriteKitButton 的自定义 SKNode 子类中使用了这个 deinit 方法,它可以工作,但这是我使用的唯一外部类。如果我将我的 SKTexture 属性设置为 weak var,那么它们在尝试显示在我的 iPad 上时会出现故障。

以上是关于即使调用了 removeAllChildren,SpriteKit 中的内存也会增加的主要内容,如果未能解决你的问题,请参考以下文章

PresentScene 内存:removeAllChildren 是不是传播?

ActionScript 3 removeAllChildren

在重新加载SKScene之前尝试使用removeAllChildren()和removeAllActions()时创建的致命错误

Java-向上转型后调用方法和属性的注意事项

即使调用了 didDetermineState,也没有调用 didrangebeacons

为啥即使使用单个 reducer 也会调用 Partitioner