For the last two weeks I've been working flat out with Rubymotion, a new toolchain that allows developers to build native iOS applications using the beautiful, elegant Ruby programming language instead of the verbose and somewhat fussy Objective-C programming language.
The app I've been working on is an existing client app we've been developing for several months. The app was about 80% ready for a pilot launch but still had quite a bit of development to go before being shipped. After discussing Rubymotion with the client, we agreed that it would save us both time and money in future development if we move over to Rubymotion. I was tasked with doing the migration; transposing the entire application to Rubymotion.
For those of you looking to do the same, here are some stats that might help you estimate the time required and the costs involved.
The migration took me twelve full working days; around 96 -> 120 hours which is a little longer than I had expected. Part of this time was learning the nuances of Rubymotion though, and a lot of it was debugging (I had some issues with TestFlight which caused the app to fail silently instead of raising exceptions. I lost about a day debugging that)!
If was to do it again now I reckon it would take me 2/3 of the time.
The app consists of 23 ViewControllers, 12 Models, 17 custom View Classes (mostly table cells), 3 class extensions and about 11 other custom classes.
So here are the stats for the app before and after:
<tr>
<td>
Ruby/Rubymotion
</td>
<td>
5604
</td>
<td>
68
</td>
</tr>
<tr>
<td>
Objective-C/XCode
</td>
<td>
5292
</td>
<td>
138
</td>
</tr>
Language/Tool | Lines of Code | Number of files |
---|
Just to be clear, the lines of code here do not include 3rd party libraries or config and settings files automatically created by either tool suite. The lines of code counted here are all lines written by me, not including white space or blank lines. The same applies to the number of files. Each file counted here is a file I've created with the exception of the app delegate files.
So at first I was a little shocked at the lines of code. Ruby's a far more succinct language than Objective-C but yet the number of lines of code was higher in the Rubymotion project than the XCode project. The explanation is that in Rubymotion I haven't used Interface Builder. Instead, all of the views are built programatically within the view controller files. While it is possible to use Interface Builder, I prefer not to.
Losing Interface Builder was actually my biggest reservation when I first saw Rubymotion but since I've started building my own views programatically I prefer it. I feel it gives me more control over the view elements and more awareness about how they are nested, aligned and configured. Granted, this is not everyone's preference though.
Here's what I love about Rubymotion so far (to name just a few things):
The most important benefit of Rubymotion is that it's faster to write code and to change existing code. When working with new apps that are prone to change a lot as more and more stake-holders throw in their 10c this is a huge advantage. Getting a prototype up and running, or a MVP to market is now faster and this should encourage development and innovation of some exciting new products in the App Store.
My single biggest bug-bear with Objective-C is that there are no modules or mixins. This usually means that the same code is written a bunch of times in different places, meaning that changes to the code become more and more costly as the app grows. Ruby's modules offer a great way to mix in code shared across various classes and they're supported in Rubymotion (although it's still not 100% perfect yet).
Rubymotion offers shortcuts for various Objective-C selectors, the most helpful of which being the objectForKey and setObject:forKey:
# objective-C
NSMutableDictionary *myOptions = [[NSMutableDictionary alloc] init];
[myOptions setObject: @"value" forKey: @"key"];
[myOptions objectForKey: @"key"]; # => @"value"
# Ruby
myOptions = {}
myOptions[:key] = "value"
myOptions[:key] # => "value"
These are a godsend and a huge help both when writing and reading code.
In Objective-C, to make a class conform to a specific protocol you have to define this in the class's interface. This can get quite tedious to keep track of, especially when you have a class that conforms to several protocols. Since protocols are not a feature of Ruby, all of this is done away with.
In Objective-C (well, Cocoa), description is a reserved word. If I give a class a property named description it will crash when I run the code. Given that this is such a common word for a model attribute, I always thought that was a pretty dumb word to reserve. Thankfully this is not an issue in Rubymotion.
XCode is clunky, and temperamental and crashes constantly. With Rubymotion I get to use any text eitor I like. My preference is TextMate! There's also a Ruby Motion Bundle for TextMate!
Rubymotion is still in it's infancy and is likely to change a lot over the coming months/years...
Apps can get pretty huge! The app I've mentioned in this post is currently at 68 files and that's likely to grow! Most developers will split these files into sub-directories based on the file's role. As a seasoned Rails developer, I instinctively opted for the following rails(ish) structure:
app/
controllers/
models/
views/
lib/
While it doesn't have to be my preferred directory structure, I'd like to see Rubymotion start to be more opinionated about how to approach things like this. This would have two advantages:
- It's easier for new developers to learn the ropes when there are standard practises in place.
- It's easier to work with another developer's code when things are laid out as you expect them to be.
I'd also like to see Rails-like generator scripts for common class types in Rubymotion. For example:
motion generate UITableViewController MyViewController
Which would generate app/controllers/my_view_controller.rb
with the following template:
class MyViewController < UITableViewController
def viewDidLoad
# do something ...
end
# == UITableViewDelegate ==
def tableView(tableView, heightForRowAtIndexPath: indexPath)
tableView.rowHeight
end
def tableView(tableView, didSelectRowAtIndexPath: indexPath)
# do something here...
end
def tableView(tableView, viewForHeaderInSection: section)
end
# == UITableViewDatasource ==
def tableView(tableView, cellForRowAtIndexPath: indexPath)
# return a table cell
end
def numberOfSectionsInTableView(tableView)
1
end
def tableView(tableView, numberOfRowsInSection: section)
10
end
def tableView(tableView, titleForHeaderInSection: section)
"My Section"
end
end
This would be a huge help!
While I do prefer writing views programatically, I don't like defining so many view items in my controller - I think this violates the principle of MVC. I've seen one proposed DSL for creating view classes in Rubymotion which would be an interesting one to watch.
While the error backtrace in Rubymotion is OK, it's not awesome. For example, sometimes the error messages are cut short of the line number. Other times they're just not that descriptive at all. It would be really great if we could get a little more feedback in the console as to what's happening when the code raises an exception.
At the moment you can build your app to a device but you can't yet see the console log output while the app is running on your device (as is possible with Xcode). If it is possible to do, this would be really useful too as I much prefer using a real device over the iOS Simulator.
I love Rubymotion and plan on using it on all of our upcoming iOS projects! Moving our client's app from Objective-C was a bit of a mission and took a lot longer than I though but, given that it should save us a lot more than two weeks of work in future, I think it was the right decision in this case. It means I can now respond to the client's feedback quicker and more comfortably!