swift – SpriteKit没有释放所有使用过的内存

前端之家收集整理的这篇文章主要介绍了swift – SpriteKit没有释放所有使用过的内存前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我已经准备好很多(如果不是全部)SO和其他网站上有关处理SpriteKit和内存问题的灾难的文章.像许多其他人一样,我的问题是在我离开我的SpriteKit场景之后几乎没有在场景会话期间添加的任何内存被释放.我试图在我发现的文章中实现所有建议的解决方案,包括但不限于……

1)确认在SKScene类中调用deinit方法.

2)确认场景类中没有对父VC的强引用.

3)强制删除所有子项和动作,并在VC消失时将场景设置为nil. (将场景设置为nil是最终被调用的deinit方法)

然而,在所有这些之后,内存仍然存在.一些背景,这个应用程序介于标准的UIKit视图控制器和SpriteKit场景之间(它是一个专业的绘图应用程序).例如,在进入SpriteKit场景之前,应用程序使用大约400 MB.进入场景并创建多个节点后,内存增长到1 GB以上(到目前为止都很好).当我离开现场时,内存可能下降100 MB.如果我重新进入现场,它会继续堆积.有关如何完全释放SpriteKit会话期间使用的所有内存的方法或建议吗?下面是一些用于尝试解决此问题的方法.

SKScene课程

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()
}

介绍VC

var scene: DrawingScene?

override func viewDidLoad(){
    let skView = self.view as! SKView
    skView.ignoresSiblingOrder = true
    scene = DrawingScene(size: skView.frame.size)
    scene?.scaleMode = .aspectFill
    scene?.backgroundColor = UIColor.white
    drawingNameLabel.text = self.currentDrawing?.name!
    scene?.currentDrawing = self.currentDrawing!

    scene?.drawingViewManager = self

    skView.presentScene(scene)
}

override func viewDidDisappear(_ animated: Bool) {
    if let view = self.view as? SKView{
        self.scene = nil //This is the line that actually got the scene to call denit.
        view.presentScene(nil)
    }
}
正如评论中所讨论的,问题可能与强大的参考周期有关.

下一步

>重新创建一个简单的游戏,其中场景被正确释放,但有些节点没有.
>我会多次重新加载这个场景.您将看到场景已正确释放,但场景中的某些节点未正确释放.每当我们用新的场景替换旧场景时,这将导致更大的内存消耗.
>我将向您展示如何使用Instruments找到问题的根源
>最后,我将向您展示如何解决问题.

1.让我们创建一个带内存问题的游戏

让我们用SpriteKit创建一个基于Xcode的新游戏.

我们需要使用以下内容创建一个新文件Enemy.swift

import SpriteKit

class Enemy: SKNode {
    private let data = Array(0...1_000_000) // just to make the node more memory consuming
    var friend: Enemy?

    override init() {
        super.init()
        print("Enemy init")
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    deinit {
        print("Enemy deinit")
    }
}

我们还需要使用以下源代码替换Scene.swift的内容

import SpriteKit

class GameScene: SKScene {

    override init(size: CGSize) {
        super.init(size: size)
        print("Scene init")
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        print("Scene init")
    }

    override func didMove(to view: SKView) {
        let enemy0 = Enemy()
        let enemy1 = Enemy()

        addChild(enemy0)
        addChild(enemy1)
    }

    override func touchesBegan(_ touches: Set<UITouch>,with event: UIEvent?) {
        let newScene = GameScene(size: self.size)
        self.view?.presentScene(newScene)
    }

    deinit {
        print("Scene deinit")
    }
}

As you can see the game is designed to replace the current scene with a new one each time the user taps the screen.

让我们开始游戏并看一下控制台.等着瞧

场景初始化
敌人初始
敌人初始

这意味着我们总共有3个节点.

现在让我们点击屏幕,让我们再次看一下控制台

场景初始化
敌人初始
敌人初始
场景初始化
敌人初始
敌人初始
场景deinit
敌人deinit
敌人deinit

我们可以看到已经创建了一个新场景和2个新敌人(第4,5,6行).最后,旧场景被解除分配(第7行),并且2个旧敌人被解除分配(第8和第9行).

所以我们内存中仍有3个节点.这很好,我们没有记忆韭菜.

如果我们使用Xcode监视内存消耗,我们可以验证每次重新启动场景时内存需求没有增加.

2.让我们创建一个强大的参考周期

我们可以在Scene.swift中更新didMove方法,如下所示

override func didMove(to view: SKView) {
    let enemy0 = Enemy()
    let enemy1 = Enemy()

    // ☠️☠️☠️ this is a scary strong retain cycle ☠️☠️☠️
    enemy0.friend = enemy1
    enemy1.friend = enemy0
    // **************************************************

    addChild(enemy0)
    addChild(enemy1)
}

如你所见,我们现在在敌人0和敌人1之间有一个强大的循环.

让我们再次运行游戏.

如果现在我们点击屏幕并看看我们将看到的控制台

场景初始化
敌人初始
敌人初始
场景初始化
敌人初始
敌人初始
场景deinit

As you can see the Scene is deallocated but the Enemy(s) are no longer removed from memory.

我们来看看Xcode Memory Report

现在,每当我们用新的场景替换旧场景时,内存消耗就会增加.

3.找到仪器的问题

当然我们确切地知道问题的确切位置(我们在1分钟前添加了强保留周期).但是,我们怎样才能在一个大项目中发现强大的保留周期呢?

让我们点击Xcode中的乐器按钮(当游戏进入模拟器时).

然后在下一个对话框中单击“传输”.

现在我们需要选择泄漏检查

Good,at this point as soon as a leak is detected,it will appear in the bottom of Instruments.

让我们让泄漏发生

回到模拟器并再次点击.场景将再次被替换.
回到乐器,等几秒钟……

这是我们的泄漏.

让我们扩展它.

仪器正在告诉我们8个敌人类型的物体已被泄露.

我们也可以选择视图Cycles和Root,Instrument会告诉我们这个

这是我们强大的保留周期!

Specifically Instrument is showing 4 Strong Retain Cycles (with a total of 8 Enemy(s) leaked because I tapped the screen of the simulator 4 times).

5.解决问题

现在我们知道问题是Enemy类,我们可以回到我们的项目并解决问题.

我们可以简单地让朋友财产变弱.

让我们更新敌人类.

class Enemy: SKNode {
    private let data = Array(0...1_000_000)
    weak var friend: Enemy?
    ...

我们可以再次检查以确认问题已经消失.

猜你在找的Swift相关文章