私はこの厄介な問題について何週間も髪を引っ張っていますが、方法や方法に関する情報やヒントが見つからないので、RubyMotionフォーラムの誰かが私を助けてくれることを願っています。
これが少し長い場合は事前にお詫びしますが、問題を適切に説明するためにいくつかの設定が必要です。背景として、Railsアプリに実装されたJSON/RESTバックエンドを使用するアプリがあります。これはかなり簡単なことです。バックエンドは正常に機能しており、ある程度まではフロントエンドも正常に機能しています。RubyMotionクライアントでモデルオブジェクトを設定するための呼び出しを行うことができ、すべてが素晴らしいです。
1つの問題は、すべてのhttp/jsonライブラリがリクエストの処理時に非同期呼び出しを使用することです。これは問題ありません。なぜ彼らがそうしているのかは理解できますが、次のステップに進む前に、返された結果を処理する必要があるため、電話を待つ必要がある状況がいくつかあります。
ユーザーが支払いをしたいと思っていて、いくつかの保存された支払い情報を持っている例を考えてみましょう。支払いオプションのリストをユーザーに提示する前に、クライアントに最新のリストがあることを確認したいと思います。したがって、現在のリスト(またはタイムアウト)を取得するユーザーオブジェクトのメソッドにリクエストを送信する必要があります。ただし、リストが最新であるか、バックエンドへの呼び出しが失敗したことが確実になるまで、続行したくありません。基本的に、結果が返されるまで、この呼び出しを(UIをブロックせずに)ブロックする必要があります。
このシナリオでは、変更のポーリングやバックエンドからフロントへの変更のプッシュなどの代替手段は適切ではありません。また、(フォームにプッシュするのではなく)宛先フォームからデータをプルすることも検討しましたが、ユーザーがゼロ、1つ、または複数の支払いオプションを持っているかどうかに応じて異なることをしたいので、この特定のシナリオでは機能しません保存しました。したがって、次のコントローラーにプッシュする前に知っておく必要があります。また、事前に知っておく必要があるのは、同期呼び出しを行うことです。
私の最初の攻撃は、リクエストの返された結果を「終了」状態とともに保存するために使用できる共有インスタンス(SyncHelperと呼びましょう)を作成することでした。リクエストが終了するまで、またはリクエストがタイムアウトするまで、CFRunLoopRunInModeを使用してスピンするだけの待機メソッドを提供できます。
SyncHelperは少しこのように見えます(私はいくつかの無関係なものを取り除くためにそれを編集しました):
class SyncHelper
attr_accessor :finished, :result, :error
def initialize()
reset
end
def reset
@finished = false
@result = nil
@error = nil
end
def finished?
@finished
end
def finish
@finished = true
end
def finish_with_result(r)
@result = r
@finished = true
end
def error?
!@error.nil?
end
def wait
timeout = 0.0
while !self.finished? && timeout < API_TIMEOUT
CFRunLoopRunInMode(KCFRunLoopDefaultMode, API_TIMEOUT_TICK, false)
timeout = timeout + API_TIMEOUT_TICK
end
if timeout >= API_TIMEOUT && !self.finished?
@error = "error: timed out waiting for API: #{@error}" if !error?
end
end
end
次に、このようなヘルパーメソッドがあります。これにより、呼び出されたメソッドにsyncrインスタンスを提供することで、同期的に呼び出しを行うことができます。
def ApiHelper.make_sync(&block)
syncr = ApiHelper::SyncHelper.new
BubbleWrap::Reactor.schedule do
block.call syncr
end
syncr.wait
syncr.result
end
私が望んでいたのは、どこでも非同期バージョンを使用することでしたが、同期的に何かを行う必要がある少数のケースでは、次のようにmake_syncブロックの周りに呼び出しをラップするだけでした。
# This happens async and I don't care
user.async_call(...)
result = ApiHelper.make_sync do |syncr|
# This one is async by default, but I need to wait for completion
user.other_async_call(...) do |result|
syncr.finish_with_result(result)
end
end
# Do something with result (after checking for errors, etc)
result.do_something(...)
重要なのは、「同期された」呼び出しから呼び出し元のコンテキストに戻り値を取得できるようにすることです。したがって、「result=...」ビットです。それができなければ、とにかく全部はあまり役に立たない。syncrを渡すことで、finish_with_resultを呼び出して、非同期タスクが完了したことをリッスンしているすべての人に通知し、呼び出し元が使用できるように結果をそこに保存できます。
make_syncとSyncHelperの実装の問題は(おそらく私が非常に愚かなことをしているという明らかな事実は別として)、BubbleWrap::Reactor.schedule内のコードが呼び出されないことです...エンドブロックが呼び出されないsyncr.waitの呼び出しがタイムアウトになるまで(注:ブロックが実行される機会がないため、結果をブロックに保存できないため、終了していません)。CFRunLoopRunInModeの呼び出しが待機中に発生している場合でも、CPUへのアクセスから他のすべてのプロセスを完全に飢えさせています。この構成のCFRunLoopRunInModeはスピン待機しますが、他のキューに入れられたブロックの実行を許可するという印象を受けましたが、それは間違っているようです。
これは、人々が時々しなければならないことだと私は思います。そのため、この種の問題に苦しんでいるのは私だけではありません。
私はあまりにも多くのクレイジーピルを持っていましたか?私が理解していない、これを行うための標準的なiOSイディオムはありますか?この種の問題を解決するためのより良い方法はありますか?
どんな助けでも大歓迎です。
前もって感謝します、
M @