この手法は RubyMotion version 2.4 では解決されています!
- Fixed a long-standing set of memory-related bugs related to lambdas. Dynamic variables (shared by lambdas and calling scope) are now allocated as heap memory inside the Proc data structure. Local variables are properly synchronized. Proc objects are properly reclaimed by the system once they are no longer used. Changes have been added to both the compiler and the runtime.
この対応によって、lambda などの中でローカル変数が参照されている場合、そのlambdaの外側のローカル変数は自動的に retain されるようになったそうです。
この対応によって なにがうれしいか? と言うと、インスタンス変数を使ってしまうと、そのインスタンスが消滅するまでメモリが消費されてしまうことや、任意のタイミングで値をセット/リセットしなければいけなくなりますが、ローカル変数だとRubyMotionのGCが適度なタイミングで解放してくれるようになります。
そのことから、長期的に見るとメモリの消費量などリソースと開発者にやさしくなるとのことです。(watson1978談)
ProMotion のドキュメントにも記述されていない上に、 ProMotion での記述方法では気付きにくいと思ったので、まとめてみました。
(すでに Objective-C などでの iOS の開発になれてる人なら、落ちる原因について想像が付くと思います)
ModalView を呼び出す側が HomeScreen 、呼び出される ModalView が ModalScreen クラスとして話を進めます。
最初にgithub上のProMotionのドキュメントにも書かれていますが、この方法だと(ModalViewを何度か開いたり閉じたりしてると)アプリが落ちたりします。
class HomeScreen < ProMotion::Screen
def open_modal
open ModalScreen.new, modal: true
end
end
class ModalScreen < ProMotion::Screen
# close_modal はUIButtonなどのactionで呼ぶ
def close_modal
close
end
end
正しくは ↓
class HomeScreen < ProMotion::Screen
def open_modal
# 一度インスタンス変数に ModalScreen を入れる
@modal_screen = ModalScreen.new
open @modal_screen, modal: true
end
# ModalScreen から dismissViewControllerAnimated:completion 経由で値を受け取れる
def on_return args
puts args[:value] # => :ok
end
end
class ModalScreen < ProMotion::Screen
# parent_screen を用意しておくことで自動的に呼び出し元の screen をセットしてくれる
attr_accessor :parent_screen
def close_modal
# parent_screen がセットされていると
# close メソッド内で正しく dismissViewControllerAnimated:completion が呼ばれる
close value: :ok
end
end
一度、インスタンス変数に格納してやることで Objective-C の strong な property になります。
modal_screen = ModalScreen.new のようにローカル変数ではダメでした。
この方法で意図しないタイミングで ModalScreen のインスタンスのメモリが解放されて落ちることがなくなります。
以下、余談
下記のように書けてもいいと思うのだけれど、現状は UIWindow が返ってくるようです この PR がmergeされたので(他の機能とconflictなどすることがなければ) version 1.0 から使えそうです
class HomeScreen < ProMotion::Screen
def open_modal
@modal_screen = open ModalScreen.new, modal: true # => UIWindow
end
end