使用BubbleWrap进行RubyMotion异步编程

时间:2012-10-31 20:57:28

标签: ruby asynchronous rubymotion single-responsibility-principle

在使用大量异步代码时,我对如何编写合适的代码感到困惑。

在下面的代码片段中,我登录以获取身份验证cookie并使用该cookie获取下一个请求以获取项目名称列表(作为示例):

def self.populateProjectsTable(projects_controller)
  payload = {email: "email", password: "pass"}
  HTTP.post("http://example.com/login", {payload: payload}) do |response|
    authCookie = response.headers['Set-Cookie']
    HTTP.get("http://example.com/projects.json", {cookie: authCookie}) do |response|
      projects = JSON.parse(response.body.to_str)
      projects_controller.projects = projects
      projects_controller.reloadData
    end
  end
end

虽然这会起作用,但代码感觉很脏。没有真正遵循单一责任原则。我想用几种方法提取这个:

def self.populateProjectsTable(projects_controller)
  @taskList = TaskList.new
  @taskList.doLogin
  projects = @taskList.getProjects
  projects_controller.projects = projects
  projects_controller.reloadData
end

def doLogin
  payload = {email: "email", password: "pass"}
  HTTP.post("http://example.com/login", {payload: payload}) do |response|
    @authCookie = response.headers['Set-Cookie']
  end
end

def getProjects
  HTTP.get("http://example.com/projects.json", {cookie: @authCookie}) do |response|
    projects = JSON.parse(response.body.to_str)
  end
end

这显然不起作用。在getProjects完成之前调用doLogin方法,并且项目仅在块的范围内已知,而不是将数据返回给populateProjectsTable方法。

如何在没有第一个示例中显示的嵌套的情况下编程此类应用程序?

3 个答案:

答案 0 :(得分:4)

你不会完全摆脱筑巢。采取艾伦的回答并稍微按摩它,这就是我想出来的。它涉及通过一些方法传递一个块。

def self.populateProjectsTable(projects_controller)
  @taskList = TaskList.new
  @taskList.loginAndGetProjects do |projects|
    projects_controller.projects = projects
    projects_controller.reloadData
  end
end

def loginAndGetProjects(&block)
  payload = {email: "email", password: "pass"}
  HTTP.post("http://example.com/login", {payload: payload}) do |response|
    @authCookie = response.headers['Set-Cookie']
    getProjects(&block)
  end
end

def getProjects(&block)
  HTTP.get("http://example.com/projects.json", {cookie: @authCookie}) do |response|
    projects = JSON.parse(response.body.to_str)
    block.call(projects)
  end
end

答案 1 :(得分:4)

我有一个类似的问题,试图包装自己占用块的方法。我希望新的包装器方法仍然可以使用块。这是我在ParseModel中所做的:

# with block:
# ParseModel::Cloud.callFunction("myFunction", {"myParam" => "myValue"}) do |result, error|
#  # do something...
# end

# without block:
# ParseModel::Cloud.callFunction("myFunction", {"myParam" => "myValue"})
module ParseModel
  class Cloud
    def self.callFunction(function, params={}, &block)
      return PFCloud.callFunction(function, withParameters:params) unless block_given?

      PFCloud.callFunctionInBackground(function, withParameters:params, block:lambda do |result, error|
        block.call(result, error)
      end)
    end
  end
end

将此概念应用于您的问题,您可以重写您的方法以自行获取块。这里有一些我认为可能有用的重构:

def self.populateProjectsTable(projects_controller)
  @taskList = TaskList.new
  @taskList.doLogin do |login_response|
    authCookie = login_response.headers['Set-Cookie']
    @taskList.getProjects(authCookie) do |projects_response|
      projects = JSON.parse(projects_response.body.to_str)
      projects_controller.projects = projects
      projects_controller.reloadData
    end
  end
end

def doLogin(&block)
  payload = {email: "email", password: "pass"}
  HTTP.post("http://example.com/login", {payload: payload}) do |response|
    block.call(response)
  end
end

def getProjects(cookie, &block)
  HTTP.get("http://example.com/projects.json", {cookie: cookie}) do |response|
    block.call(response)
  end
end

我不认为你对SRP完全不了解,但这应该是一个好的开始。

答案 2 :(得分:1)

为Jamon的回答+1。

如果您喜欢SRP,我可能会建议使用类来管理您的会话并将API拆分为模块。在添加其他API调用时,这尤其有用。在这里,我排队一旦登录完成就会满足的请求。稍后您可以添加超时处理等。

module ProjectApi
  def get_projects(&block)
    with_session do
      HTTP.get("http://example.com/projects.json", {cookie: @auth_cookie}) do |response|
        projects = JSON.parse(response.body.to_str)
        block.call(projects)
      end
    end
  end
end

class MySession
  include ProjectApi

  def initialize(login, password)
    @login = login
    @password = password
    @state = nil
    @requests = []
  end

  def active?
    @state == :active
  end

  def with_session(&block)
    @requests << &block
    active? ? handle_requests : login(true)
  end

  private

  def login(do_handle_requests = false)
    payload = {login: @login, password: @password}
    @state = nil
    HTTP.post("http://example.com/login", {payload: payload}) do |response|
      @state = :active
      @auth_cookie = response.headers['Set-Cookie']}
      handle_requests if do_handle_requests
    end
  end  

  def handle_requests
    while request = @requests.shift do
      request.call
    end if active?
  end    

end

def self.populateProjectsTable(projects_controller)
  @session ||= MySession.new('mylogin', 'mypassword')
  @session.get_projects do |projects|
    projects_controller.projects = projects
    projects_controller.reloadData
  end
end