Watson's Blog

RubyMotion アプリのメモリ関連の不具合を簡単に探す方法

| Comments

事前に「問題:次のRubyMotionアプリにはメモリ関連の不具合があります。5分以内に原因箇所を特定せよ。」という記事を書いておきました。今回はこの問題の解決方法を書きたいと思います。まだ読まれていない方は、まずそちらから読んでみてください。

1. Instrumentsを使う

RubyMotion 2.12でInstrumentsを簡単に使えるようになりました。まずは、Instrumentsを使用して不具合があるかどうか確認しておきましょう。今回は"Zombies"というテンプレートを用います。

1
% rake profile template="Zombies"

rake profile:templatesを実行するとほかにどのようなテンプレートがあるか確認できます。

Instrumentsが起動したあと、アプリがクラッシュする手順を実行します。

131206-0001.png

メモリが解放されてしまったオブジェクトにメッセージを送信してクラッシュしていることがわかります。残念ながら、シンボルが削除されていてなにが原因なのかまではわかりません。

“Zombie Messaged"と表示されない場合には、今回の手順では不具合箇所を探すことができないので、別の方法を模索してください。

2. 意図せず解放されているオブジェクトを探す

以下のコードをapp/debug.rbなどの名前で保存してアプリに追加しておきます。

app/debug.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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にログがいろいろ表示されます。

131206-0002.png

MyControllerdeallocされていることがわかります。運良く、すぐ上にautoreleaseのログがありますね。app_delegate.rbの47行目でautoreleaseが呼ばれていることが分かります。該当する箇所は、以下のコードとなっています。

app/app_delegate.rb
1
MyController.new

コントローラのオブジェクトが気づかないうちに解放されたためにクラッシュしていました。インスタンス変数に格納して、長期的に保有するようにするとアプリが期待通りに動作するようになります。

app/app_delegate.rb
1
@controller = MyController.new

運が良いと、このような感じで簡単に原因箇所を割り出すことができます。万能ではないので、運が悪いと原因が見つからないかもしれません。

deallocautoreleaseを上書きすることでメモリリークが発生したりすることもありますので、使い終わったらすぐに削除してください。

3. 余談

RubyObjectではなくNSObjectに対してメソッドをオーバーライドしてみたのですが、うまくいかなかったのでこのようになっています。 StringクラスがString->NSMutableString->NSString->NSObjectと継承しているせいなのかdeallocで文字列を扱うと、文字列生成->dealloc->文字列生成->dealloc->…と無限に繰り返されてしまい、クラッシュしてしまいます。

組み込みのStringクラスに影響を与えないためにRubyObjectを使用しています。おおよそ以下の図のような構成になっているはずです。このため、組み込みクラスやiOS SDKのクラスを継承しているものや、それらが影響して解放されてしまっているようなものは、今回の方法では検出できません。

Comments