用于等待异步执行的块的iOS(或RubyMotion)习惯用法是什么?

在这个棘手的问题上,我已经把头发拉了几个星期了,我找不到关于如何做或做什么的任何信息或提示,所以我希望RubyMotion论坛上有人可以帮助我。

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

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

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

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

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

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

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

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

我有太多疯狂的药片吗? 是否有一个标准的iOS习惯用于这样做,我只是不理解? 有没有更好的方法来解决这类问题?

任何帮助将非常感激。

提前致谢,

米@

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

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

如果您需要首先显示数据,请在viewDidAppear中执行; 如果它发生在某个操作上,则将您的转换移动到您的网络成功块/回调中的下一个视图(performSegueWithIdentifier或其他),并在调用该操作时进行网络调用。

您的网络库中应该有如何示例,或者查看MBProgressHUD本身的使用示例代码https://github.com/jdg/MBProgressHUD 。

这是我做multithreading同步异步调用的方法。

 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 

正如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上处理它的方式

您还可以使用同步队列。

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

请查看更多使用示例: http : //blog.arkency.com/2014/08/concurrent-patterns-in-rubymotion/