等待异步执行的块的 iOS(或 RubyMotion)习语是什么? [英] What is the iOS (or RubyMotion) idiom for waiting on a block that executes asynchronously?

查看:17
本文介绍了等待异步执行的块的 iOS(或 RubyMotion)习语是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经为这个琐碎的问题烦恼了好几个星期,但我找不到任何关于如何或做什么的信息或提示,所以我希望 RubyMotion 论坛上的人可以帮助我.

I have been pulling my hair out for weeks on this niggling problem, and I just can't find any info or tips on how or what to do, so I'm hoping someone here on the RubyMotion forums can help me out.

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

Apologies in advance if this is a little long, but it requires some setup to properly explain the issues. As background, I've got an app that uses a JSON/REST back-end implemented ina Rails app. This is pretty straightforward stuff. The back-end is working fine, and up to a point, so is the front end. I can make calls to populate model objects in the RubyMotion client and everything is great.

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

The one issue is that all of the http/json libs use async calls when processing requests. This is fine, and I understand why they are doing it, but there are a couple of situations where I need to be able to wait on a call because I need to do something with the returned results before proceeding to the next step.

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

Consider the example where a user wants to make a payment, and they have some saved payment information. Prior to presenting the user with a list of payment options, I want to make sure that I have the latest list at the client. So I need to make a request to a method on the user object that will grab the current list (or timeout). But I don't want to continue until I am sure that the list is either current, or the call to the back-end has failed. Basically, I want this call to block (without blocking the UI) until the results are returned.

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

Alternatives such as polling for changes or pushing changes from the back-end to the front are not appropriate for this scenario. I also considered simply pulling the data from the destination form (rather than pushing it into the form) but that doesn't work in this particular scenario because I want to do different things depending on whether the user has zero, one or multiple payment options saved. So I need to know in advance of pushing to the next controller, and to know in advance, I need to make a synchronous call.

我的第一个攻击是创建一个共享实例(我们称之为 SyncHelper),我可以使用它来存储请求的返回结果以及完成"状态.它可以提供一个等待方法,该方法只使用 CFRunLoopRunInMode 旋转,直到请求完成,或者直到请求超时.

My first attack was to create a shared instance (let's call it the SyncHelper) that I can use to store the returned result of the request, along with the "finish" state. It can provide a wait method that just spins using CFRunLoopRunInMode either until the request is finished, or until the request times out.

SyncHelper 看起来有点像这样(我已经对其进行了编辑以删除一些不相关的内容):

SyncHelper looks a bit like this (I've edited it to take some irrelevant stuff out):

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

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

Then I have a helper method like this, which would allow to me to make any call synchronous via the provision of the syncr instance to the invoked method.

def ApiHelper.make_sync(&block)
  syncr = ApiHelper::SyncHelper.new
  BubbleWrap::Reactor.schedule do
    block.call syncr
  end
  syncr.wait
  syncr.result
end

我希望在任何地方都使用异步版本,但在少数需要同步执行某些操作的情况下,我会简单地将调用包装在 make_sync 块周围,如下所示:

What I had hoped to do was use the async versions everywhere, but in the small number of cases where I needed to do something synchronously, I would simply wrap the call around a make_sync block like this:

# 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,告诉任何监听异步任务已经完成的人,并将结果存储在那里供调用者使用.

Importantly, I want to be able to get the return value from the 'synchronised' call back into the invoking context, hence the 'result =...' bit. If I can't do that, then the whole thing isn't much use to me anyway. By passing in syncr, I can make a a call to its finish_with_result to tell anyone listening that the async task has completed, and store the result there for consumption by the invoker.

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

The problem with my make_sync and SyncHelper implementations as they stand (apart from the obvious fact that I'm probably doing something profoundly stupid) is that the code inside the BubbleWrap::Reactor.schedule do ... end block doesn't get called until after the call to syncr.wait has timed out (note: not finished, because the block never gets the chance to run, and hence can't store result in it). It is completely starving all other processes from access to the CPU, even tho the call to CFRunLoopRunInMode is happening inside wait. I was under the impression that CFRunLoopRunInMode in this config would spin wait, but allow other queued blocks to run, but it appears that I've got that wrong.

这让我觉得这是人们不时需要做的事情,所以我不可能是唯一遇到此类问题的人.

This strikes me as something that people would need to do from time-to-time, so I can't be the only person having trouble with this kind of problem.

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

Have I had too many crazy pills? Is there a standard iOS idiom for doing this that I'm just not understanding? Is there a better way to solve this kind of problem?

任何帮助将不胜感激.

提前致谢,

M@

推荐答案

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

When you need to display the payment options, display a HUD, like MBProgressHUD to block the user from using the UI and then start your network call. When the network call returns, dismiss the HUD in either in your success/failure blocks or in the delegate methods and then refresh your view with the data received.

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

If you don't like the HUD idea you can display something appropriate in your UI, like a UILabel with "loading..." or an UIActivityIndicatorView.

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

If you need to get the data to display first thing, do it in viewDidAppear; if it happens on an action then move your transition to the next view (performSegueWithIdentifier or whatever) into your network success block/callback and make the network call when the action is called.

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

There should be examples in your networking library of how, or take a look at the usage sample code in MBProgressHUD itself https://github.com/jdg/MBProgressHUD.

这篇关于等待异步执行的块的 iOS(或 RubyMotion)习语是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆