2

私はこの厄介な問題について何週間も髪を引っ張っていますが、方法や方法に関する情報やヒントが見つからないので、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 @

4

4 に答える 4

3

支払いオプションを表示する必要がある場合は、MBProgressHUDなどのHUDを表示して、ユーザーがUIを使用できないようにしてから、ネットワーク通話を開始します。ネットワーク呼び出しが戻ったら、成功/失敗ブロックまたはデリゲートメソッドのいずれかでHUDを閉じてから、受信したデータでビューを更新します。

HUDのアイデアが気に入らない場合は、「loading ...」を含むUILabelやUIActivityIndi​​catorViewなど、UIに適切なものを表示できます。

最初に表示するデータを取得する必要がある場合は、viewDidAppearで実行してください。アクションで発生した場合は、次のビュー(performSegueWithIdentifierなど)への遷移をネットワーク成功ブロック/コールバックに移動し、アクションが呼び出されたときにネットワーク呼び出しを行います。

ネットワークライブラリにその方法の例があるはずです。または、MBProgressHUD自体の使用例コードhttps://github.com/jdg/MBProgressHUDを見てください。

于 2013-03-26T00:59:50.317 に答える
2

マルチスレッドの同期非同期呼び出しを行うために私が行うことは次のとおりです。

def make_sync2(&block)
  @semaphore ||= Dispatch::Semaphore.new(0)
  @semaphore2 ||= Dispatch::Semaphore.new(1)
  BubbleWrap::Reactor.schedule do
    result = block.call("Mateus")
    @semaphore2.wait # Wait for access to @result
    @result = result
    @semaphore.signal
  end
  @semaphore.wait # Wait for async task to complete
  result = @result
  @semaphore2.signal
  result
end
于 2013-03-26T00:20:03.427 に答える
1

borrrdenが言ったように、私はdispatch_semaphoreを使用します

 def ApiHelper.make_sync(&block)
   @semaphore = Dispatch::Semaphore.new(0)
   BubbleWrap::Reactor.schedule do
     # do your stuff
     @result = block.call()
     @semaphore.signal
   end
   @semaphore.wait
   @result
 end

これがRubymotionでの処理方法です

于 2013-03-25T09:41:13.243 に答える
1

同期キューを使用することもできます。

Dispatch::Queue.new('name').sync

Dispatch::Queue.main.sync

その他の使用例をご覧ください:http://blog.arkency.com/2014/08/concurrent-patterns-in-rubymotion/

于 2014-08-24T06:13:15.940 に答える