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的新游戏.
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") } }
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? ...
我们可以再次检查以确认问题已经消失.