2

几个星期以来,我一直在为这个琐碎的问题而烦恼,但我找不到任何关于如何或做什么的信息或提示,所以我希望 RubyMotion 论坛上的某个人可以帮助我。

如果这有点长,请提前道歉,但需要一些设置才能正确解释问题。作为背景,我有一个使用在 Rails 应用程序中实现的 JSON/REST 后端的应用程序。这是非常简单的事情。后端工作正常,在某种程度上,前端也是如此。我可以调用在 RubyMotion 客户端中填充模型对象,一切都很好。

一个问题是所有 http/json 库在处理请求时都使用异步调用。这很好,我理解他们为什么这样做,但是在某些情况下我需要能够等待呼叫,因为我需要在继续下一步之前对返回的结果做一些事情。

考虑一个用户想要付款的例子,他们有一些保存的付款信息。在向用户提供付款选项列表之前,我想确保我在客户处拥有最新列表。所以我需要向用户对象上的一个方法发出请求,该方法将获取当前列表(或超时)。但我不想继续,直到我确定列表是最新的,或者对后端的调用失败。基本上,我希望这个调用阻塞(不阻塞 UI),直到返回结果。

诸如轮询更改或将更改从后端推送到前端等替代方案不适合这种情况。我还考虑过简单地从目标表单中提取数据(而不是将其推送到表单中),但这在这种特定情况下不起作用,因为我想根据用户是否有零个、一个或多个支付选项来做不同的事情保存。所以我需要提前知道push到下一个控制器,并且提前知道,我需要进行同步调用。

我的第一个攻击是创建一个共享实例(我们称之为 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

然后我有一个像这样的辅助方法,它允许我通过将同步器实例提供给被调用的方法来使任何调用同步。

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(...)

重要的是,我希望能够将“同步”调用的返回值返回到调用上下文中,因此是“结果 = ...”位。如果我不能做到这一点,那么整件事对我来说也没多大用处。通过传入syncr,我可以调用它的finish_with_result,告诉任何监听异步任务已经完成的人,并将结果存储在那里供调用者使用。

我的 make_sync 和 SyncHelper 实现存在的问题(除了我可能在做一些非常愚蠢的事情这一显而易见的事实)是 BubbleWrap::Reactor.schedule 中的代码 do ... end 块没有被调用直到对 syncr.wait 的调用超时(注意:未完成,因为该块永远没有机会运行,因此无法将结果存储在其中)。它完全使所有其他进程无法访问 CPU,即使对 CFRunLoopRunInMode 的调用是在等待中发生的。我的印象是这个配置中的 CFRunLoopRunInMode 会旋转等待,但允许其他排队的块运行,但似乎我错了。

这让我觉得这是人们不时需要做的事情,所以我不能是唯一一个遇到这种问题的人。

我有太多疯狂的药丸吗?是否有一个标准的 iOS 习语来做这件事,我只是不理解?有没有更好的方法来解决这类问题?

任何帮助将非常感激。

提前致谢,

男@

4

4 回答 4

3

当您需要显示支付选项时,显示一个 HUD,如 MBProgressHUD 以阻止用户使用 UI,然后开始您的网络呼叫。当网络调用返回时,在您的成功/失败块或委托方法中关闭 HUD,然后使用接收到的数据刷新您的视图。

如果您不喜欢 HUD 的想法,您可以在 UI 中显示适当的内容,例如带有“正在加载...”的 UILabel 或 UIActivityIndi​​catorView。

如果您需要首先显示数据,请在 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

您还可以使用同步队列。

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 回答
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 回答