Elevate
Are you suffering from symptoms of Massive View Controller?
Use Elevate to elegantly decompose tasks, and give your view controller a break.
Introduction
Your poor, poor view controllers. They do so much for you, and they get rewarded with even more responsibilities:
- Handle user input
- Update the UI in response to that input
- Request data from web services
- Load/save/query your model layer
In reality, view controllers attract complexity because they often act as a conceptual junk drawer of glue code. Fat controllers are major anti-pattern in Rails, yet iOS controllers are tasked with even more concerns.
Elevate is your view controller's best friend, shouldering some of these burdens. It cleanly separates the unique behavior of your view controller (that is, what it is actually meant to do) from the above concerns, letting your view controller breathe more. Ultimately, Elevate makes your view controllers easier to understand and modify.
This is a rather bold claim. Let's look at an example:
class ArtistsViewController < UIViewController
  include Elevate
  def viewDidLoad
    super
    launch(:update)
  end
  task :update do
    background do
      response = Elevate::HTTP.get("https://example.org/artists")
      DB.update(response)
      response
    end
    on_start do
      SVProgressHUD.showWithStatus("Loading...")
    end
    on_finish do |response, exception|
      SVProgressHUD.dismiss
      self.artists = response
    end
  end
endWe define a task named update. Within that task, we specify the work that it does using the background method of the DSL. As the name implies, this block runs on a background thread. Next, we specify two callback handlers: on_start and on_finish. These are run when the task starts and finishes, respectively. Because these are alwys run on the main thread, you can use them to update the UI. Finally, in viewDidLoad, we start the task by calling launch, passing the name of the task.
Notice that it is very clear what the actual work of the update task is: getting a list of artists from a web service, storing the results in a database, and passing the list of artists back. Thus, you should view Elevate as a DSL for a very common pattern for view controllers:
- Update the UI, telling the user you're starting some work
- Do the work (possibly storing it in a database)
- Update the UI again in response to what happened
(Some tasks may not need steps 1 or 3, of course.)
Taming Complexity
You may have thought that Elevate seemed a bit heavy for the example code. I'd agree with you.
Elevate was actually designed to handle the more complex interactions within a view controller:
- Async HTTP: Elevate's HTTP client blocks, letting you write simple, testable I/O. Multiple HTTP requests do not suffer from the Pyramid of Doom effect, allowing you to easily understand dataflow. It also benefits from...
- Cancellation: tasks may be aborted while they are running. (Any in-progress HTTP request is aborted.)
- 
Errors: exceptions raised in a backgroundblock are reported to a callback, much likeon_finish. Specific callbacks may be defined to handle specific exceptions.
- Timeouts: tasks may be defined to only run for a maximum amount of time, after which they are aborted, and a callback is invoked.
The key point here is that defining a DSL for tasks enables us to abstract away tedious and error-prone functionality that is common to many view controllers, and necessary for a great user experience. Why re-write this code over and over?
Documentation
To learn more:
Requirements
- 
iOS 5 and up or OS X 
- 
RubyMotion 2.x - Elevate pushes the limits of RubyMotion. Please ensure you have the latest version before reporting bugs! 
Installation
Update your Gemfile:
gem "elevate", "~> 0.7.0"
Bundle:
$ bundle install
Inspiration
This method of organizing work recurs on several platforms, due to its effectiveness. I've stolen many ideas from these sources:
- Hexagonal Architecture
- Android: AsyncTask
- .NET: BackgroundWorker
- Go's goroutines for simplifying asynchronous I/O
License
MIT License