Elevate
Stop scattering your domain logic across your view controller. Consolidate it to a single conceptual unit with Elevate.
Example
@login_task = async username: username.text, password: password.text do
task do
# This block runs on a background thread.
#
# The @username and @password instance variables correspond to the args
# passed into async. API is a thin wrapper class over Elevate::HTTP,
# which blocks until the request returns, yet can be interrupted.
credentials = API.login(@username, @password)
if credentials
UserRegistration.store(credentials.username, credentials.token)
end
# Anything yielded from this block is passed to on_update
yield "Logged in!"
# Return value of block is passed back to on_finish
credentials != nil
end
on_start do
# This block runs on the UI thread after the operation has been queued.
SVProgressHUD.showWithStatus("Logging In...")
end
on_update do |status|
# This block runs on the UI thread with anything the task yields
puts status
end
on_finish do |result, exception|
# This block runs on the UI thread after the task block has finished.
SVProgressHUD.dismiss
if exception == nil
if result
alert("Logged in successfully!")
else
alert("Invalid username/password!")
end
else
alert(exception)
end
end
end
Background
Many iOS apps have fairly simple domain logic that is obscured by several programming 'taxes':
- UI management
- asynchronous network requests
- I/O-heavy operations, such as storing large datasets to disk
These are necessary to ensure a good user experience, but they splinter your domain logic (that is, what your application does) through your view controller. Gross.
Elevate is a mini task queue for your iOS app, much like Resque or Sidekiq. Rather than defining part of an operation to run on the UI thread, and a CPU-intensive portion on a background thread, Elevate is designed so you run the entire operation in the background, and receive notifications when it starts and finishes. This has a nice side effect of consolidating all the interaction for a particular task to one place. The UI code is cleanly isolated from the non-UI code. When your tasks become complex, you can elect to extract them out to a service object.
In a sense, Elevate is almost a control-flow library: it bends the rules of iOS development a bit to ensure that the unique value your application provides is as clear as possible. This is most apparent with how Elevate handles network I/O: it provides a blocking HTTP client built from NSURLRequest for use within your tasks. This lets you write your tasks in a simple, blocking manner, while letting Elevate handle concerns relating to cancellation, and errors.
Features
- Small, beautiful DSL for describing your tasks
- Actor-style concurrency
- Simplifies asynchronous HTTP requests when used with Elevate::HTTP
- Built atop of NSOperationQueue
Installation
Update your Gemfile:
gem "elevate", "~> 0.5.0"
Bundle:
$ bundle install
Usage
Include the module in your view controller:
class ArtistsSearchViewController < UIViewController
include Elevate
Launch an async task with the async method:
- Pass all the data the task needs to operate (such as credentials or search terms) in to the
asyncmethod. - Define a block that contains a
taskblock. Thetaskblock should contain all of your non-UI code. It will be run on a background thread. Any data passed into theasyncmethod will be available as instance variables, keyed by the provided hash key. - Optionally:
- Define an
on_startblock to be run when the task starts - Define an
on_finishblock to be run when the task finishes - Define an
on_updateblock to be called any time the task calls yield (useful for relaying status information back during long operations)
- Define an
All of the on_ blocks are called on the UI thread. on_start is guaranteed to precede on_update and on_finish.
@track_task = async artist: searchBar.text do
task do
artist = API.track(@artist)
ArtistDB.update(artist)
end
on_start do
SVProgressHUD.showWithStatus("Adding...")
end
on_finish do |result, exception|
SVProgressHUD.dismiss
end
end
To cancel a task (like when the view controller is being dismissed), call cancel on the task returned by the async method. This causes a CancelledError to be raised within the task itself, which is handled by the Elevate runtime. This also prevents any callbacks you have defined from running.
NOTE: Within tasks, do not access the UI or containing view controller! It is extremely dangerous to do so. You must pass data into the async method to use it safely.
To Do
- Need ability to set timeout for tasks
Caveats
- Must use Elevate's HTTP client instead of other iOS networking libs
Inspiration
- Hexagonal Architecture
- Android SDK's AsyncTask
- Go (asynchronous IO done correctly)
License
MIT License

