资产单例和参考周期

时间:2019-01-30 17:10:43

标签: swift memory-management memory-leaks sprite-kit retain-cycle

我目前有一个Assets单例类,可让我访问纹理,声音和音乐。当我和我的合伙人进入项目的内存管理阶段时,我们意识到可能会造成严重的泄漏,根据我对Xcode仪器的使用,我们最大的问题可能围绕单例类。虽然肯定还有其他泄漏,但我们注意到,在地图屏幕和游戏屏幕之间来回移动时,会相当稳定地增加〜100 mb,这似乎对应于我们的11个地图资产。在这种情况下,我的问题是:

下面的代码是否会创建一个保留周期,如果是,则可以使用单例类的存在对其进行管理,还是应该将其分解。纹理图集是分开保存的吗?

func transitionToMapScreen()
    {
        //I hope this isn't necessary eventually, but we were trying to ensure all game textures and emitters were deallocated
        Assets.sharedInstance.deallocateGameAssets()

        gameScene = GameScene()

        Assets.sharedInstance.preloadMap
        {
            [unowned self] in

            let mapScene = MapScreen(fileNamed: "MapScreen")!
            mapScene.preCreate()
            mapScene.scaleMode = self.scaleMode

                // Transition with a fade animation
                let reveal = SKTransition.fade(withDuration: 2.0)

                let fadeMusic = SKAction.run
                {
                    Assets.sharedInstance.bgmTitlePlayer?.setVolume(1.0, fadeDuration: 1.0)

                    Assets.sharedInstance.bgmTitlePlayer?.play()

                    Assets.sharedInstance.bgmGamePlayer?.setVolume(0.0, fadeDuration: 1.0)
                }

                let stopGameMusic = SKAction.run
                {
                    Assets.sharedInstance.bgmGamePlayer?.stop()
                }

                let transitionAction = SKAction.run
                {
                    self.view?.presentScene(mapScene, transition: reveal)
                }

                self.run(SKAction.sequence([SKAction.wait(forDuration: 1.0), fadeMusic, SKAction.group([stopGameMusic, transitionAction])]))

        } // end Assets.sharedInstance.preloadMap completion block*/
    }

据我对Swift中的保留周期的了解,这不是在创建对Assets类的自引用并造成内存泄漏吗?这可以解释我们的地图资产保留在内存中的行为吗?如果是这样,管理此问题的正确方法是什么?

1 个答案:

答案 0 :(得分:1)

我想在这里张贴这篇文章,供那些可能正在寻找与解决保留周期相关的类似问题的答案的人解释内存增长问题。首先,非常感谢所有帮助我停止神经元的随机疯狂尝试的人,这些尝试试图寻找那些没有(或者没有足够大的东西)的保留周期。现在:

首先,可以肯定的是,保留周期很可怕,但是使用工具来找到保留周期,然后按照Apple的建议进行管理,因为Swift 4.2适用:

something()
{
    [weak self] in 

    guard let self = self else { return }

    self.whatever()
}

我已经看到有人认为您应该确定无主还是弱有道理-诚实地说,这消除了猜测工作,并且容易得多。我确实发现,至少对于我们来说,无人认领的崩溃很少发生,但是我不会对您的应用程序发表意见,这可以解决问题。现在,一旦您打扫房子:

我们发现,内存增长问题不是源于我们的资产单例类,而是源于纹理图集的庞大大小以及这些图集的相应重叠用法。我对此讨论的建议不够充分:How does SKTexture caching and reuse work in SpriteKit?。这将在概念上比我更好地解释您可能会遇到的问题。

但是,总结一下:SpriteKit管理纹理图集的分配,因此,您必须了解,如果您经常加载非常大的图集,则可能无法按预期进行管理(我仍然没有足够的详细信息以更好的方式描述它,但正如我所说,请参考上面的讨论以及有关SKTextureAtlas的Apple开发人员指南:https://developer.apple.com/documentation/spritekit/sktextureatlas)。

现在,与苹果的讨论有关,我注意到了这一行,我认为应该以黑体显示该行:“当访问地图集的纹理之一时,SpriteKit隐式加载地图集。”这对于解决我认为是我们潜在问题的解决方案至关重要:由于某些原因,我们有几个地方可以通过单个实例访问图集中的纹理-您必须意识到,在大图集的情况下,SpriteKit会加载您的整个庞大的地图集都进入了记忆。因此,我不再掉以Apple的便条来管理您的地图集大小。地图集适用于总是一起使用并且将被汇总在一起的资产。将不同的资产载入地图集是我们的错误。我们正在重新组织相应的管理方式。