关于 iOS9 个问题的 Spritekit removeFromParent()

Spritekit removeFromParent() on iOS9 issues

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

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

我认为最安全的版本应该是下面这段代码,它严重失败并出现上述异常:

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

所以总结一下:child 节点仍然有一个 parent (self.child.parent != nil)。即使在其 parent 的 children 列表中找到 child1 也会说:嘿,我还在那里,po child1 和 po child1.parent!显示有效节点 objects。但 removeFromParent() 仍然会使应用程序崩溃。

deinit() 中的日志消息显示每个 object...

只调用一次去初始化器

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

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 次单击时,将以编程方式删除所有节点,这会使应用程序崩溃。

任何想法表示赞赏。这是 Apple 的错误还是我遗漏了什么?

XCode 7 GM,在模拟器中崩溃并在 IPhone5...

首先这句话

The most safe version I think should be the following piece of code and it badly fails with the mentioned exception as well:

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

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

1。 ContainerNode 中的 deinit 错误

当您删除节点的 parent 时(并且没有其他对该节点的强引用),它会自动(感谢 ARC)删除并释放其内存。 而且,非常重要的是,这会递归地发生 children.

所以你的 ContainerNode class 变成:

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 个。

希望对您有所帮助。

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

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

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

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