jjzjj

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

coder 2023-06-04 原文

我已经在 SO 和其他网站上准备了许多(如果不是全部)关于处理 SpriteKit 和内存问题的灾难的文章。我的问题和其他许多人一样,是在我离开 SpriteKit 场景后,几乎没有释放场景 session 期间添加的任何内存。我已经尝试在我找到的文章中实现所有建议的解决方案,包括但不限于...

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

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

3) 强制移除所有children和actions,当VC消失时将scene设置为nil。 (将场景设置为 nil 是最终调用 deinit 方法的原因)

然而,毕竟,内存仍然存在。一些背景知识,这个应用程序介于标准 UIKit View Controller 和 SpriteKit 场景(它是一个专业的绘图应用程序)之间。例如,应用程序在进入 SpriteKit 场景之前使用了大约 400 MB。进入场景并创建多个节点后,内存增长到 1 GB 以上(目前一切正常)。当我离开现场时,内存下降可能 100 MB。如果我重新进入现场,它会继续堆积。关于如何完全释放 SpriteKit session 期间使用的所有内存,有什么方法或建议吗?以下是一些用于尝试解决此问题的方法。

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

介绍风投

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

最佳答案

正如评论中所讨论的,该问题可能与强引用循环有关。

后续步骤

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

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

我们可以看到创建了一个新场景和 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 之间有一个强循环

让我们再次运行游戏。

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

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

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

让我们看看 Xcode 内存报告

现在,每次我们用新场景替换旧场景时,内存消耗都会增加。

3。使用 Instruments 查找问题

我们当然知道问题出在哪里(我们在一分钟前添加了强保留周期)。但是我们如何才能在大型项目中检测到强大的保留周期呢?

让我们点击 Xcode 中的 Instrument 按钮(当游戏在模拟器中运行时)。

让我们在下一个对话框中点击Transfer

现在我们需要选择Leak Checks

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

4。让我们让泄漏发生

返回模拟器并再次点击。场景将再次被替换。 返回Instruments,等待几秒钟然后...

这是我们的泄漏。

让我们展开它。

Instruments 准确地告诉我们 8 个 Enemy 类型的对象已被泄露。

我们还可以选择 View 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 类上,我们可以回到我们的项目并解决问题。

我们可以简单地将 friend 属性设为 weak

让我们更新 Enemy 类。

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

我们可以再次检查以确认问题已解决。

关于swift - SpriteKit 没有释放所有使用的内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47663042/

有关swift - SpriteKit 没有释放所有使用的内存的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  7. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  8. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

随机推荐