事前に「問題:次のRubyMotionアプリにはメモリ関連の不具合があります。5分以内に原因箇所を特定せよ。」という記事を書いておきました。今回はこの問題の解決方法を書きたいと思います。まだ読まれていない方は、まずそちらから読んでみてください。
追記:Instrumentsを使うだけで不具合箇所が分かるようです → RubyMotion アプリのメモリ関連の不具合を簡単に探す方法(2)
1. Instrumentsを使う
RubyMotion 2.12でInstrumentsを簡単に使えるようになりました。まずは、Instrumentsを使用して不具合があるかどうか確認しておきましょう。今回は"Zombies"というテンプレートを用います。
% rake profile template="Zombies"
rake profile:templates
を実行するとほかにどのようなテンプレートがあるか確認できます。
Instrumentsが起動したあと、アプリがクラッシュする手順を実行します。
メモリが解放されてしまったオブジェクトにメッセージを送信してクラッシュしていることがわかります。残念ながら、シンボルが削除されていてなにが原因なのかまではわかりません。
"Zombie Messaged"と表示されない場合には、今回の手順では不具合箇所を探すことができないので、別の方法を模索してください。
2. 意図せず解放されているオブジェクトを探す
以下のコードをapp/debug.rb
などの名前で保存してアプリに追加しておきます。
class RubyObject
def dealloc
puts "-" * 80
puts "dealloc #{self.inspect}"
super
end
def autorelease
puts "=" * 80
puts "autorelease #{self.inspect}"
p caller
super
end
end
RubyMotionやObjective-Cでは、オブジェクトが解放されるときにdealloc
というメソッドが呼ばれます。また解放される前にautorelease
が呼び出され解放するように指示されます。
意図せずdealloc
が呼ばれているオブジェクトを探し、autorelease
が呼ばれる場所がどこなのかを特定します。アプリを実行するとTerminalにログがいろいろ表示されます。
MyController
がdealloc
されていることがわかります。運良く、すぐ上にautorelease
のログがありますね。app_delegate.rb
の47行目でautorelease
が呼ばれていることが分かります。該当する箇所は、以下のコードとなっています。
MyController.new
コントローラのオブジェクトが気づかないうちに解放されたためにクラッシュしていました。インスタンス変数に格納して、長期的に保有するようにするとアプリが期待通りに動作するようになります。
@controller = MyController.new
運が良いと、このような感じで簡単に原因箇所を割り出すことができます。万能ではないので、運が悪いと原因が見つからないかもしれません。
dealloc
とautorelease
を上書きすることでメモリリークが発生したりすることもありますので、使い終わったらすぐに削除してください。
3. 余談
RubyObject
ではなくNSObject
に対してメソッドをオーバーライドしてみたのですが、うまくいかなかったのでこのようになっています。
String
クラスがString
->NSMutableString
->NSString
->NSObject
と継承しているせいなのかdealloc
で文字列を扱うと、文字列生成->dealloc->文字列生成->dealloc->...と無限に繰り返されてしまい、クラッシュしてしまいます。
組み込みのString
クラスに影響を与えないためにRubyObject
を使用しています。おおよそ以下の図のような構成になっているはずです。このため、組み込みクラスやiOS SDKのクラスを継承しているものや、それらが影響して解放されてしまっているようなものは、今回の方法では検出できません。