Pretty image
Clay shows how Ruby and Objective-C can coexist in iOS app development.

I’ve always been in the middle of two engineering worlds. I started off writing Mac and iOS apps, software defined by Apple’s constraints and the type-checked realm of Objective-C. But once I needed to make my apps networked and dynamic, I ventured into the Wild West of Ruby, where frameworks and best practices came and went like the boom-bust towns of old.

I kept on and straddled the lines for a few years. The two languages had so much in common, but sometimes seemed miles apart. If only we could unite iOS development with Ruby’s penchant for expressive frameworks and DSLs; if only we could get rid of the veryVerbose:withLongNames: Objective-C methods but still create those beautiful native iPhone apps....

And then, in May 2011, RubyMotion rolled into town. RubyMotion, from HipByte, is a toolchain that compiles Ruby into LLVM machine code run under the Objective-C runtime. In other words, you can write iPhone apps with Ruby. It’s proprietary and semi-closed-source, but is based on and made by the same developers as the MacRuby project.

Aha! At last, here was something for me! But does coding in Ruby really make writing iOS apps more pleasant? Well, let’s take a look at what the RubyMotion tools and community have cooked up in comparison to the canonical Objective-C workflow. These will all be pretty high-level examples, just a fun little survey to whet your appetite and pique your interest.

I dive a lot deeper into this in my recently released Pragmatic Bookshelf book, RubyMotion: iOS Development with Ruby, a trip from zero-to-app in about 100 pages that assumes just some basic Ruby experience.

So much for the plug; let’s take a look at the most popular RubyMotion library: BubbleWrap.

The Big One: BubbleWrap

Shortly after RubyMotion was released, the innocently-named BubbleWrap appeared sporting a few concise methods for sending HTTP requests. Since then, BubbleWrap has grown to a nifty treasure chest of wrappers and abstractions used in just about every RubyMotion project.

What do these “wrappers” look like? Well, for example, you might want to take a photo with the device’s camera. In the original Objective-C framework, there was a system of delegate methods and bit-mask configuration and all other sorts of un-Ruby constructs. But with BubbleWrap, it’s pretty simple:

 BW::Device.camera.front.picture do |result|
  image = result[:original_image]
 end

Same goes for grabbing the location using the phone’s GPS; in fact, the iOS SDK implementation for geolocation was even more extensive than the camera’s! But with RubyMotion, it’s just these few simple lines:

 BW::Location.get do |result|
  from = result[:from]
  p "From Lat #{from.latitude}, Long #{from.longitude}"
  to = result[:to]
  p "To Lat #{to.latitude}, Long #{to.longitude}"
 end

BubbleWrap also contains a myriad of tiny wrappers which, although just bits of sugar, are small wins in programmer happiness. For example, you know those blue popups that show up every now and again in iOS apps? They’re represented by simple UIAlertView objects, but the syntax for spawning them remains characteristically verbose from Objective-C-land:

 alert = UIAlertView.alloc.initWithTitle("Hello",
  message: "I'm an Alert",
  delegate: nil,
  cancelButtonTitle: "OK",
  otherButtonTitles: nil)
 alert.show

Naturally, BubbleWrap provides a small wrapper to save our fingers some trouble:

 App.alert("Hello", message: "I'm an Alert")

BubbleWrap is filled with this sort of little addition, along with the bigger wrappers around more complex APIs like HTTP requests, playing videos, UI customization, and more. Most of BubbleWrap’s abstractions and techniques can indeed be implemented with modern Objective-C, but that community is tepid towards any efforts that go against the Apple-tinted grain.

These are nice tricks, but they don’t really highlight Ruby’s unique strengths. Perhaps we need to draw inspiration elsewhere.

There’s Rails in My iOS!

When RubyMotion first arrived, a lot of folks immediately thought, “Hey, now we can port Rails to iOS!” That’s an interesting idea, but mobile is just too fundamentally different from a web backend: unlike a server, mobile is a stateful, evented, often resource-constrained soup where you can’t simply git push to squash bugs. But differences aside, maybe Rails does have a few habits iOS can pick up without copying it verbatim.

One place in need of improvement is Models, with a capital M. The Apple-provided iOS framework for Models is Core Data, which has an unfortunately steep learning curve; ActiveRecord, on the other hand, is fairly painless to get up and running in a typical Rails project. This is where MotionModel steps up to bat.

MotionModel is a RubyMotion gem for creating Models using an ActiveRecord-esque DSL. Take a look at the canonical Task object:

 class Task
  include MotionModel::Model
  include MotionModel::Validatable
  columns :name => :string,
  :description => :string,
  :due_date => :date,
  :location => :string
  validates :name => :presence => true
 end

A good start, right? Once you’ve defined a model, you can do queries and persistence with ease:

 @tasks = Task.where(:description).contains('urgent')
  .and(:location).contains('seattle')
 @task = Task.create(:name => 'Walk the Dog',
  :description => 'Remember the leash',
  :due_date => '2012-09-15')
 @task.description = 'Don't forget the leash'
 @task.save

Since .create just takes a Hash, it could be a perfect fit for using in tandem with a JSON-based API. Beyond the basics, it sports many ActiveRecord-like features like relationships:

 class Task
  ...
  has_many :assignees
 end
 class Assignee
  include MotionModel::Model
  columns :assignee_name => :string
  belongs_to :task
 end
 @task = Task.create(:name => "Walk the Dog")
 @task.assignees.create(:assignee_name => "Howard")
 @assignee = Assignee.new(:name => "Douglas")
 @task.assignees << @assignee

There’s more to MotionModel than these few code samples, but it frankly beats the pants off doing work in Core Data. Now, is MotionModel as performant or battle-hardened as Apple’s own approach? Probably not, but for 80% of apps it should do the trick with a warm-fuzzy familiarity.

Speaking of Rails, one thing the non-mobile Ruby community certainly got right is the emphasis on automated tests. Accordingly, RubyMotion ships with a fantastic testing framework.

Unit Test, Then Unit Test Some More

One point I see brought up time and time again among iOS developers is the difficulty of writing automated tests. Apple’s framework, UIAutomation, is actually... a JavaScript framework, tightly integrated with their Instruments application. And installing any third-party Objective-C testing library usually involves setting up multiple targets in Xcode and the whole process just escalates very quickly. Why can’t it be as simple as Rails, just one rake test away?

Well, in RubyMotion it is! Technically it’s the rake spec command, but otherwise everything works the same: you add test files to your project’s spec folder, use an RSpec-like DSL to organize each test, and then fill in your assertions and expectations. Here’s an example taken from the open-sourced and well-spec’d TinyMon RubyMotion app:

 describe Account do
  it "should extract attributes" do
  account = Account.new(:name => 'Test',
  :status => 'success', :role => 'user')
  account.name.should == 'Test'
  account.status.should == 'success'
  account.role.should == 'user'
  end
 end

Objective-C testing frameworks use all sorts of C preprocessor macros and lack the plain-English readability you can see above. Ruby tests, Motion or not, are among the most pleasant to write and maintain.

And don’t get me started on how to test the user interface with Xcode! Thankfully, RubyMotion provides an equally expressive API for simulating user interaction. Again, taken from the TinyMon example:

 describe LoggedInViewDeckController do
  tests LoggedInViewDeckController
  it "should reveal menu on swipe" do
  table = view('success.png').up(UITableView)
  flick table, :to => :right
  point = table.superview.convertPoint(table.frame.origin,
  toView:nil)
  point.x.should > 150
  end
 end

Nifty, right? There’s also support for tap, pinch, and all manner of custom gestures. You can pretty much read RubyMotion tests word-for-word and they’ll make sense as paragraphs and sentences. And again, these are all runnable via the rake spec command, so they can fit into your automated or continuous integration workflow.

Before RubyMotion, writing iOS tests was hard, and usually just didn’t happen because the barriers to entry were so high. But now, with the power of Ruby and its seamless workflow, my own development habits are just a bit shy of completely test-driven.

Building User Interfaces

The stuff we’ve looked at so far mainly helps us with the skeleton and operation of our app, but what iOS apps are really known for is how they look and feel. Can RubyMotion help out with that, too? As in our Views, with a capital V?

Turns out, yes! One of the other popular RubyMotion libraries is Formotion. Formotion is a DSL for creating form-based interfaces, kind of like what you see in the Contacts or Settings apps. Its claim to fame is that you can create an interface like this:

tOGkm.jpg

Formotion Preview

Using just this Hash:

 @form = Formotion::Form.new({
  sections: [{
  title: "Register",
  rows: [{
  title: "Email",
  key: :email,
  placeholder: "me@mail.com",
  type: :email,
  auto_correction: :no,
  auto_capitalization: :none
  }, {
  title: "Password",
  key: :password,
  placeholder: "required",
  type: :string,
  secure: true
  }, {
  title: "Password",
  subtitle: "Confirmation",
  key: :confirm,
  placeholder: "required",
  type: :string,
  secure: true
  }, {
  title: "Remember?",
  key: :remember,
  type: :switch,
  }]
  }]
 })
 @form_controller = Formotion::FormController.alloc.initWithForm(@form)
 @window.rootViewController = @form_controller

This type of interface is actually incredibly tedious to create from scratch with UITableViews, so Formotion easily saves dozens to hundreds of lines of code in the process.

I think this is the most exciting development in the RubyMotion community: creating wholly new metaphors and workflows, beyond just bits of abstraction and syntactic sugar, for how we make mobile apps. This sort of thing, for technical or cultural reasons, just isn’t taking hold in the original iOS/Objective-C world.

A More Perfect Union

There’s a weird vibe in both the Objective-C and RubyMotion communities that there has to be an either/or choice between the two; that one technology has to be “good” and the other must be “bad” or “wrong.” But the thing is, the two can coexist quite pleasantly in one project. You can take your Ruby with a side of C, or cook up an Objective-C soufflé using a light Ruby dusting. At least one of those sounds delicious, right?

Because at the end of the day, both RubyMotion and plain-old Objective-C apps get compiled down to the same LLVM machine code. As in, if you use UIView.alloc.initWithFrame(CGRectZero) in RubyMotion, it should produce the exact same instructions as writing [[UIView alloc] initWithFrame:CGRectZero] in Xcode. This has a really great side effect: RubyMotion projects can bundle Objective-C code, and Objective-C projects can include libraries written in RubyMotion. They’re totally interoperable!

There are a couple ways to achieve this unity. RubyMotion supports CocoaPods, the Objective-C equivalent of RubyGems, out of the box. Simply add a CocoaPod to your Rakefile and voila!

 require 'motion-cocoapods'
 Motion::Project::App.setup do |app|
  app.pods do
  pod 'AFNetworking'
  end
 end

The reason CocoaPods works so readily is RubyMotion supports building arbitrary Xcode and Objective-C files into an app, which CocoaPods already builds upon. For example, let’s say you wanted to include this small Objective-C class into your RubyMotion project:

 @interface CYAlert : NSObject
 + (void)show;
 @end
 @implementation CYAlert
 + (void)show {
  UIAlertView *alert = [[UIAlertView alloc] init];
  alert.title = @"This is Objective-C";
  alert.message = @"Mixing and matching!";
  [alert addButtonWithTitle:@"OK"];
  [alert show];
  [alert release];
 }
 @end

All you need to do is instruct the RubyMotion builder to also include the proper files in the process:

 Motion::Project::App.setup do |app|
  # ./vendor/CYAlert contains CYAlert.h and CYAlert.m
  app.vendor_project('vendor/CYAlert', :static)
 end

This is incredibly useful if you want to include non-standard libraries or memory-sensitive code that isn’t directly supported in RubyMotion. That :static option also accepts :xcode, which lets you drop in entire Xcode projects as opposed to raw files. This also means you can wean your codebase to or from Ruby, so switching isn’t an all-or-nothing game.

Finally, RubyMotion supports the compilation of your Ruby files into a single static library with the rake static task. The generated library can then be added to any normal Xcode projects and used within Objective-C code! I’ve read of developers writing their specs in Ruby to be run against Objective-C code in this manner.

The Road Ahead

I hope this gave you a better idea of how RubyMotion looks and feels in your editor. If you want to dive deeper and learn iOS properly, take a glance at my RubyMotion book and see if it’s right for you. Or if you want to see what else is coming out of the RubyMotion community, take a peek at RubyMotion-Wrappers.com, an excellent collection of recent projects and RubyGems.

At the end of the day, the choice to use RubyMotion or Objective-C is decided by the demands of your project: will the DSLs and wrappers in RubyMotion speed things up, or would the lack of Xcode integration slow development down? Engineering is all about tradeoffs, and we can only aim to be as educated as possible on all the decisions we have to make.

Clay Allsopp is a hacker, Thiel Fellow, and internet enthusiast. An iOS developer since day one, Clay has crafted beautiful mobile apps with over a million cumulative downloads for startups like Circle. He is currently building Propeller, the best way for anyone to build a mobile app.

Send the author your feedback or discuss the article in the magazine forum.