即使调用了 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
(或SKSpriteNode
或SKLabelNode
)时,您应该只写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()时创建的致命错误