2014年5月8日木曜日

SpriteKitでハマった話。 iOS7.1, SpriteKit, SKShapeNode, EXC_BAD_ACCESS的な。

次に何かリリースしようとSpriteKitを使って簡単なゲームを作っていたところハマりました。
先に書いておくと未解決です。ボスケテ。

環境:iOS 7.1.1, XCode 5.1.1
状況:SKShapeNodeを子に持ったSKSceneがメモリから解放される時にEXC_BAD_ACCESSで落ちる場合がある

SKShapeNodeを子に持ったSKSceneをビューで保持していない状態で別のシーンに遷移したり、意図的に開放してやったりすると
SKCSprite::removeSubSprite(SKCSprite*)
の呼び出しでEXC_BAD_ACCESSで落ちる。

こうなって
こうなる

試しにXCodeのSpriteKitのテンプレートに丸いSKShapeNodeを1つ追加してサイズと背景色だけ設定したSceneに遷移しても再現しました。

自分の使い方が間違っているのかと思い、ググってみると何やらiOS7.1固有のSpriteKitのバグかもとか言われてる・・・?
http://stackoverflow.com/questions/22399278/sprite-kit-ios-7-1-crash-on-removefromparent

確かに7.1の実機,シミュレーターだと再現し、7.0のシュミレーターだと再現しない様子。

試しに別クラスからShapeNodeを保持するようにして、上記フォーラムのやり方で先に開放するようにしても改善せず。
メモリ位置の関係なのか、ShapeNodeの生成を
・呼び出し箇所で直書き
・同一クラス内で関数化して呼び出し
・サブクラス経由で生成
と変えてみると、再現率が変わったりと不安定。
Zombiesにも引っかからないし。
いっそ全てのシーンをビューから保持したままにしてやろうかとか、頭を抱えてしまいました。

SKShapeNodeのサブクラスや該当のSKSceneのサブクラスで
- (void)dealloc
{
    if(self.children.count > 0){

        [self removeAllChildren];
    }
}

とかやっても結局スーパークラスで
SKCSprite::removeSubSprite(SKCSprite*)
が呼ばれてしまい駄目っぽい。
SKShapeNode本体にクラス拡張をしてみるとやっぱりこいつのdeallocで落ちている。
一応、他のSKNodeの純正サブクラスでも試しましたが、ShapeNode以外は問題ない様子。
試行回数がそれほど多くないので断言は出来ませんが。
つまり

・SKSceneがAutoReleaseで解放される際に
・開放されるSceneに含まれるSKShapeNodeにdeallocが呼ばれる
と、
・メモリの状態によりEXC_BAD_ACCESS が起きる場合がある

という状況らしい。
・・・らしい。むむむ。

海外のフォーラムでも話題になっていましたが、SKScene自体がメモリ周りが不明瞭な部分があるようで、その関連なのでしょうか?

どうにも先に進まないのでひとまず諦めて

1.CAShapeLayerでオフスクリーンに描画
2.オフスクリーンの内容をCGImageに変換
3.できたCGImageを使ってSKTexture作成
4.SKTexture使ってSKSpriteNodeを作成

とかなんとかしてSKShapeNodeは使わずにSKSpriteNodeで代替することに。
あまりに悩みすぎて「Shape」と名の付くクラスを使うのに戦々恐々としましたが、CALayerは問題ない様子。そりゃそうだ。

結局解決していない上に確定情報がなにもないというグダグダな記事ですみません。

「俺、原因知ってるZE!」
「それはお前が悪いだけだ」
「調べ方が足りないだけで解決策が既出」
など、ご指摘あればコメントいただけると幸いです。