iOS9上的spritekit removeFromParent()问题

时间:2015-09-28 15:04:58

标签: ios swift ios9 xcode7 sknode

我的应用根据传入的事件动态创建多个SKNode和SKSpriteNodes。当我尝试清理过时的节点及其包含的子节点时,我使用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()中的日志消息显示每个对象只调用一次deinitializer ...

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

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演示中(它加载演示的gamescene文件)。在背景上单击6次。前5次点击将节点添加到场景中。在第6次点击所有节点将以编程方式删除,然后应该使应用程序崩溃。

任何想法都赞赏。这是Apple的错误还是我错过了什么?

XCode 7 GM,模拟器和IPhone5崩溃......

2 个答案:

答案 0 :(得分:0)

首先这句话

  

我认为最安全的版本应该是以下代码段,并且它也严重失败,并提到了异常:

永远不会表示存在此符号的代码块!:)

现在,您的代码中存在一些错误的内容,但基本上您过度移动您的节点。

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

出于某种原因,你从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个节点。

enter image description here

希望这有帮助。

答案 1 :(得分:0)

我最终找到了坠机的原因。

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

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

不要同时从多个主题更新场景图。