几个星期以来,我一直在为这个琐碎的问题而烦恼,但我找不到任何关于如何或做什么的信息或提示,所以我希望 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 习语来做这件事,我只是不理解?有没有更好的方法来解决这类问题?
任何帮助将非常感激。
提前致谢,
男@