【问题标题】:Spritekit removeFromParent() on iOS9 issues关于 iOS9 问题的 Spritekit removeFromParent()
【发布时间】:2015-09-28 15:04:57
【问题描述】:

我的应用根据传入事件动态创建多个 SKNode 和 SKSpriteNode。当我尝试清理过时的节点及其包含的子节点时,我使用 node.removeFromParent()。

但是,一旦我尝试在所有节点层上使用 deinit 的清理代码,我立即得到 EXC_BAD_ACCESS。

我认为最安全的版本应该是以下代码,它也因上述异常而严重失败:

deinit {
   if self.child1.parent!.children.contains(self.child1) {
       self.child1.removeFromParent()
   }
}

总结一下:子节点还有一个父节点(self.child.parent != nil)。即使在其父母的孩子列表中找到 child1 也会说:嘿,我还在那儿, po child1 和 po child1.parent!显示有效的节点对象。但是 removeFromParent() 仍然会使应用崩溃。

deinit() 中的日志消息表明,每个对象仅调用一次反初始化器...

我能够在一个小项目中重现它。基本的东西应该包含在这两个类中:

import SpriteKit

public class ContainerNode : SKNode {

    private let myNode : CustomNode

    override init() {
        self.myNode = CustomNode()
        super.init()
        self.addChild(self.myNode)
    }

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

        deinit {
            self.myNode.removeFromParent()
            self.removeFromParent()
        }

    }

    public class CustomNode : SKNode {

        private let containerNode : SKNode
        private let child1 : SKNode

        override public init() {

            self.child1 = SKNode()

            self.containerNode = SKNode()

            super.init()

            self.containerNode.addChild(self.child1)

            self.addChild(self.containerNode)
        }

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

        deinit {

            if self.child1.parent!.children.contains(self.child1) {
                self.child1.removeFromParent()  // EXC_BAD_ACCESS here !!
            }

            self.containerNode.removeFromParent()
            self.removeFromParent()
        }

    }

class GameScene: SKScene {

    private var myNodes = [ContainerNode]()

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

        if myNodes.count >= 5 {

            self.removeAllChildren()

            self.myNodes.removeAll()

        }

        for touch in touches {

            let location = touch.locationInNode(self)

            let node = ContainerNode()
            node.position = location

            myNodes.append(node)

            self.addChild(node)

        }
    }
}

class GameViewController: UIViewController {

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        if let scene = GameScene(fileNamed:"GameScene") {

            UIApplication.sharedApplication().idleTimerDisabled = true

            let skView = self.view as! SKView

            skView.multipleTouchEnabled = true;
            scene.scaleMode = .ResizeFill

            skView.presentScene(scene)
        }
    }

    override func shouldAutorotate() -> Bool {
        return true
    }

    override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
        if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
            return .AllButUpsideDown
        } else {
            return .All
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    override func prefersStatusBarHidden() -> Bool {
        return true
    }
}

代码可以粘贴到标准的 Spaceship 演示中(它会加载演示的游戏场景文件)。在背景上单击 6 次。前 5 次单击将节点添加到场景中。在第 6 次单击时,所有节点都将以编程方式被删除,这将使应用程序崩溃。

任何想法表示赞赏。这是苹果的错误还是我错过了什么?

XCode 7 GM,在模拟器和 iPhone5 上崩溃...

【问题讨论】:

  • 添加了控制器和视图类。游戏场景文件是 XCode 中 Spaceship 演示的开箱即用场景文件。

标签: ios swift ios9 xcode7 sknode


【解决方案1】:

首先是这句话

我认为最安全的版本应该是以下代码,它也因上述异常而严重失败:

应该从不指出有这个符号!的代码块:)

现在,您的代码中有一些错误的地方,但基本上您过度删除您的节点。

1。 ContainerNode 中的 deinit 错误

当您删除一个节点的父节点(并且没有其他对该节点的强引用)时,它会自动被删除(感谢 ARC)并释放其内存。 而且,非常重要的是,这会递归地发生在它的孩子身上。

所以你的ContainerNode 类变成:

public class ContainerNode : SKNode {

    private let myNode : CustomNode

    override init() {
        self.myNode = CustomNode()
        super.init()
        self.addChild(self.myNode)
    }

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

//  deinit {
//      self.myNode.removeFromParent()
//      self.removeFromParent()
//  }
}

2。 CustomNode 也有同样的问题

请按照以下说明更新您的代码。

public class CustomNode : SKNode {

    private let containerNode : SKNode
    private let child1 : SKNode

    override public init() {

        self.child1 = SKNode()

        self.containerNode = SKNode()

        super.init()

        self.containerNode.addChild(self.child1)

        self.addChild(self.containerNode)
    }

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

//  deinit {
//      
//      if self.child1.parent!.children.contains(self.child1) {
//          self.child1.removeFromParent()  // EXC_BAD_ACCESS here !!
//      }
//      
//      self.containerNode.removeFromParent()
//      self.removeFromParent()
//  }

}

3。游戏视图控制器

由于某种原因,您从 GameViewController 中删除了这一行

skView.showsNodeCount = true

如果你将它添加到你的代码中,让我们在这里说

class GameViewController: UIViewController {

    override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

    if let scene = GameScene(fileNamed:"GameScene") {

        UIApplication.sharedApplication().idleTimerDisabled = true

        let skView = self.view as! SKView

        skView.multipleTouchEnabled = true;
        scene.scaleMode = .ResizeFill

        skView.presentScene(scene)

        skView.showsNodeCount = true // <<< add this
    }
}

您将能够在屏幕的右下角看到添加到图表中的节点数。您会看到每次点击后都会添加 4 个新节点。 在第 6 次之后,所有节点(但场景除外)都被删除,然后立即再次添加 4。

希望这会有所帮助。

【讨论】:

  • W.r.t to 1) 你是对的 :) 我想指出的条件是 parent 确实还活着并且是有效的对象 - 但是 removeFromParent() 仍然因为某种原因而挣扎。如果 parent 为 nil ,则条件将失败而不是 removeFromParent() ,并且很明显不再有 parent。
  • 您的代码中有几处不必要地过于复杂。而这个self.child1.parent!.children.contains(self.child1) 就是结果。在 SpriteKit(感谢 ARC)中,当不再引用节点时会自动释放节点。你应该依赖这个机制而不是添加复杂的逻辑:-)
  • W.r.t 2)你说如果没有更多的引用......但是我对子节点有很强的引用,即节点类中的成员(myNode,containerNode,child1)所以如果调用 deinit在父节点上,它首先从节点树中删除子节点(通过子节点的 deinit),从父节点的 deinit 返回后,父节点消失(也称为 ARC 收集)。
  • 你是对的。当我试图在 SKCRenderer 中找到一个问题时,整个复杂性增加了,该问题在单独的运行循环中因错误的访问异常而失败。
  • 只需更新我在回答中指出的代码,然后将此方法deinit { debugPrint("CustomNode deallocated") } 添加到CustomNode 并将此deinit { debugPrint("ContainerNode deallocated") } 添加到ContainerNode。您将在控制台上看到您的节点现在已正确释放。
【解决方案2】:

我终于找到了崩溃的原因。

SpriteKit 根本不是线程安全的,我以多线程方式使用它(基本渲染循环加上一个单独的线程接收网络数据包,它在同一个线程中也更新了 SpriteKit 的场景图)。传入网络数据包的不可预测性导致了零星且难以重现的崩溃。

Apple 的错误报告得到了相同的解释:

不要同时从多个线程更新场景图。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-07
    • 2015-12-19
    • 2016-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多