RubyMotion Tutorial for Objective C Developers

RubyMotion is a great alternative to writing iOS (and OS X) apps with Objective C. Many RubyMotion users seem to come from Ruby backgrounds and are new to iOS development. On the other hand, some are Objective C developers with little or no Ruby experience who wants to pick up RubyMotion. This tutorial is for the latter group and gives examples for iOS. The same principles and tools apply for OS X development.

Table of Contents

  1. Syntax
  2. More Code Differences
  3. Tools
  4. Create and Manage a Project
  5. Building and Running a Project
  6. Using External Objective C Code
  7. Using External RubyMotion Code
  8. A Few Nice Alternatives in Ruby
  9. Grand Central Dispatch
  10. Constants
  11. Memory Management
  12. Gotchas
  13. Storyboard and Core Data
  14. Testing on Device
  15. Submission
  16. Additional Notes
  17. Very Useful Resources
  18. What's Next
  19. Acknowledgments

Syntax

A note about naming conventions in Ruby. Instead of CamelCase for variables and methods, Ruby uses snake_case for variables and methods. Ruby uses CamelCase for classes (and modules) like Objective C. For the purpose of this guide, we are sticking with CamelCase for variables and methods so it's easier to compare the differences between Objective C and RubyMotion.

Objective C maps pretty well to Ruby as a language. At the most basic level, you'll just be writing Objective C code in Ruby syntax. Here's a couple of examples:

[NSDate date];
[[UIApplication sharedApplication] canOpenURL:aURL];
[aNavigationController pushViewController:aViewController animated:YES];

becomes:

NSDate.date #Or NSDate.date()
UIApplication.sharedApplication.canOpenURL(aURL)
aNavigationController.pushViewController(aViewController, animated:true)

To sum it up

  1. Method calls use . instead of square brackets
  2. Methods in Ruby have optional parentheses ()
  3. Note the translation from Objective C-style keyword messages to RubyMotion, notably the commas.
  4. true/false instead of YES/NO
  5. There is no trailing semicolon

In addition:

[self someMethod];
self.title = @"Some Title";
BOOL enabled = view.userInteractionEnabled;
[someObject setObject:someValue forKey:@"someKey"];
id obj = [someObject objectForKey:@"someKey"];
someMethod #Or self.someMethod
self.title = 'Some Title' #Setter, can't omit self like the last line
enabled = view.userInteractionEnabled?
someObject[someKey] = someValue
obj = someObject["someKey"]
  1. Objective C properties/getter/setters (e.g. -title and -setTitle:) map directly to Ruby accessor methods (#title and #title=)
  2. self is optional when calling a method that is not a setter
  3. Objective C methods of the format -isXxx (e.g. -isUserInteractionEnabled) (and thus BOOL properties such as userInteractionEnabled) map to Ruby accessor methods #xxx? (e.g. #userInteractionEnabled?)
  4. -objectForKey: and -setObject:forKey: map to #[] and #[]= respectively similar to how -objectForKeyedSubscript: and -setObject:forKeyedSubscript: in Objective C behaves.
- (NSDate*)someMethod {
    NSDate* date = [NSDate date];
    return date;
}
def someMethod
  date = NSDate.date
  date
end

def someMethod
  NSDate.date #Of course just this 1-liner is good too
end
  1. You don't need to declare the type of a variable
  2. The result of the last expression is implicitly returned. You can still use the return keyword like in Objective C, especially for guard clauses.
@interface SomeClass : NSObject

@property (nonatomic,strong) NSString* name;

@end

@implementation SomeClass

@synthesize name;

//If we want to implement our own setter to do something
- (void)setName(NSString*)aString {
    name = aString;
}

@end
class SomeClass < NSObject
  attr_accessor :name

  #If we want to implement our own setter to do something
  def name=(aString)
    @name = aString
  end
end
  1. No header files, no separate class declaration. Code goes into .rb files
  2. Ruby uses < to indicate subclassing
  3. Use the @ prefix to refer to instance variables. Instance variables are created the first time they are accessed. #attr_accessor defines a getter (#name) and setter (#name=) that defaults to the ivar @name. Instance variables are private. Note that unlike declaring properties in Objective C with @property which is a compile-type construct, #attr_accessor is a runtime construct. #attr_accessor is just a method call (remember parentheses are optional).
@interface SomeClass : NSObject

+ (void)aClassSideMethod;
- (void)aInstanceSideMethod;

@end

@implementation SomeClass

+ (void)aClassSideMethod {}
- (void)aInstanceSideMethod {}

@end
class SomeClass

def self.aClassSideMethod do
end

def aInstanceSideMethod do
end

Instance side and class side methods definition.

NSArray* array = @[1, 2, 3];
NSDictionary* dict = @{@"key1": @"val1", @"key2": @"val2"};
array = [1, 2, 3]
dict = {"key1" => "val1", "key2" => "val2"}

The String, Array and Hash classes in Ruby are subclasses of NSMutableString, NSMutableArray, NSMutableDictionary respectively in RubyMotion, so they have both their Objective C “interfaces” (i.e. methods) as well as Ruby interfaces.

UIView* v =[[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 100)];
CGSize s = GSizeMake(100, 20);
v = UIView.alloc.initWithFrame([[0, 0], [320, 100]])
#This is ok too: UIView.alloc.initWithFrame(CGRectMake(0, 0, 320, 100))
s = [100, 20]
  1. There's a shortcut for creating CGRect(s) by passing a 2-D array.
  2. There's a similar shortcut for creating CGSize(s) by passing an array.
if (yesOrNo) {
    //do something
} else if (anotherYesOrNo) {
    //do something else 1
} else {
    //do something else 2
}
if yesOrNo do
    #do something
elsif anotherYesOrNo do
    #do something else 1
else
    #do something else 2
end

Instead of braces {}, Ruby mostly use some form of do-end "block"-style code blocks.

- (id)initWithNibName:(NSString*)nibName bundle:(NSBundle*)nibBundle {
    if (self = [super initWithNibName:nibName bundle:nibBundle]) {
        //Some initialization
    }
    return self;
}
def initWithNibName(nibName, bundle:nibBundle)
    super
    self
end

In Ruby, super sends a message to the superclass version of the current method with the same arguments.

for (NSString* e in array) {
    [self doSomethingWithString:e];
}
array.each do |e|
    doSomethingWithString(e)
end

array.each {|e| doSomethingWithString(e) }

Array has an #each that supports 2 styles of iteration.

@throw [NSException exceptionWithName:@"Foo" reason:@"Bar" userInfo:nil];
raise "foo"
raise SomeException.new("foo")

Unlike in Objective C, where exceptions have poorer performance and are not often used in application-level code, Ruby uses exceptions extensively. raise creates an exception of type RuntimeError as a default.

@try {
    //do something
} @catch (SomeException* ex) {
    //deal with it
} @finally {
    //something that always needs to run
}
begin
    #do something
rescue Exception => ex
    #deal with it, access ex
ensure
    #something that always needs to run
end

Handling exceptions. Like Objective C's @finally, ensure is optional.

begin
    #do something
rescue MoreSpecificException
    #deal with it
rescue Exception => ex
    #deal with it, access ex
else
    #deal with other type of exceptions
end

begin
    #do something
rescue #defaults to catching StandardError
    #deal with it
end

You can handle multiple, specific types of exceptions, and in addition have a “catch-all” else.

More Code Differences

NSLog(@"hello world");
NSLog("hello world")
puts "hello world" #or puts("hello world"), but Rubyist usually call p and puts without parentheses

Both #p and #puts print out an object to the console but not to the device log. The former prints the result of sending #inspect to the object while the latter prints the result of sending #to_s to the object. NSLog is still available.

RubyMotion classes are built on top of Objective C classes as subclasses of their Objective C equivalent, so they have both their original Objective C “interfaces” (i.e. methods) as well as Ruby interfaces. Note that String, Array and Hash subclasses from NSMutableString, NSMutableArray and NSMutableDictionary respectively. i.e. they are all mutable.

In additional to String, there is a class called Symbol in Ruby which is a subclass of String:

s = 'This is a string'
sym = :some_symbol

Symbol is a subclass of String. SymbolString, Array and Hashes are immutable and are useful as a sort of replacement of (Objective) C enums, but refrain from using them as keys for Hashes. Serializing and deserializing them can be tricky since they'll come back as Strings and not Symbols when you deserialize them.

[anObject performSelector:@selector(someMethod)];
anObject.performSelector('someMethod')
anObject.send(:someMethod)

In addition to -performSelector:, you can use #send: in Ruby passing in a symbol for the method name.

There is no code preprocessor, so conditional compiles can't be used. You can still use if-else statements during runtime of course. This means that if you have been using #defines as compile time constructs to strip development code that uses Apple private frameworks/methods for testing, you'll need a replacement technique since all code is compiled and submitted to the app store. (You can modify your Rakefile so certain files aren't included when peforming app store submission builds, for example).

You can replace #define constants with class or module constants:

class MyClass
  SOME_CONSTANT = 'value 1'

  def some_method
    var1 = SOME_CONSTANT
  end
end

module MyModule
  ANOTHER_CONSTANT = 'value 2'
end

var2 = MyClass::SOME_CONSTANT
var3 = MyModule::ANOTHER_CONSTANT

In Ruby, blocks, procs and lambdas that are variations of the same concept, some kind of closure object like Objective C blocks. Note that in RubyMotion, they all compile down to instances of the Proc class. You'll most commonly use blocks and procs.

[aView animateWithDuration:0.7 animations:^{
    //do something
}];
aView.animateWithDuration(0.7, animations:proc {
    #do something
})

obj.doSomethingWithAProc(proc {|v1, v2|
    #do something with args v1 and v2
})

That is a proc.

array.each do |e|
    doSomethingWithString(e)
end

The do-end is a block.

def thisMethodTakesAProc(p)
    #can invoke proc with p.call() passing in arguments separated by comma
end

def thisMethodTakesABlock(&block)
    #can invoke block with block.call() too
end

Different syntax for methods taking in procs and blocks as arguments.

NSError* err;
[anObj doSomethingWithPossibleError:&err];
NSLog(@"Error: %@", err);
err = Pointer.new(:object)
p err[0] #read
err[0] = someObject #write

There isn't a built-in way to manipulate pointers in Ruby like in Objective C. Instead, RubyMotion provides a Pointer class where you specify the type of pointer as a symbol, you'll use :object for all objects that are not C primitives and a corresponding symbol for each primitive type (e.g. :char for char*). You reference the pointer by using #[]. You assign a value it by using #[]=.

Tools

Tooling for RubyMotion is quite different from Objective C. Instead of primarily using the Xcode IDE as the project configuration tool, build tool and editor, you use command line tools provided by RubyMotion and bring your own editor.

  1. rake – the defacto make-like and task management tool for Ruby. One key difference is it uses Ruby. RubyMotion comes with various rake tasks to do things like build your project, run it and run test suites. You can also extend it by adding your own tasks. This is the single command line tool you will use most of the time.
  2. Rakefile – This is the configuration file for rake. Your project settings and configurations are specified here. This includes things such as your app name, bundle ID and libraries to use.
  3. motion – This is a RubyMotion-specific tool that lets you do a few less common and usually non-project specific tasks. You create a new RubyMotion project, download RubyMotion updates and file support tickets with motion.
  4. editor – If you have been using Xcode as your primary code editor, you'll have to switch to another text editor of your choice.

Create and Manage a Project

Instead of create a new project in Xcode, you run the following to create a new project:

motion create my_new_project

This will create the directory my_new_project as well as print out the list of files it creates for you. Other than some set up stuff, the vanilla Rakefile generated is just this:

Motion::Project::App.setup do |app|
  app.name = 'my_new_project'
end

Don't be fooled, there are other settings that are “inherited” from the defaults. You can run rake config to see the complete list of properties that are set (and which can be overridden). Not every possible property is exposed to direct configuration via the app object, but you can stuff those additional properties like this by using app.info_plist:

app.info_plist['UIRequiresPersistentWiFi'] = true
app.info_plist['CFBundleIcons'] = { 'CFBundlePrimaryIcon' => { 'UIPrerenderedIcon' => true }}

Unlike using Xcode where you need to explicitly add source and header files to the project, every .rb file in app/ is automatically included. Font files (.ttf and .otf) and images that are in the resources/ directory are also automatically included. You do not need to specify them.

Building and Running a Project

Running rake by itself runs the default task which builds and runs the app in the simulator. It drops you into a read-eval-print-loop (REPL) console while the simulator is running.

When you run the vanilla app generated by motion create, the simulator appears to run but just shows a black screen. This is because no window has been created. Here's how code looks like in app/app_delegate.rb

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    true
  end
end

Using what we have learnt so far, we can modify it to:

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.rootViewController = ViewController.new
    @window.makeKeyAndVisible
    true
  end
end

class ViewController < UIViewController
  def initWithNibName(nibName, bundle:nibBundle)
    super
    self.view.backgroundColor = UIColor.greenColor
    self.title = 'Hello World'
    self
  end
end

Run it again, and the simulator should launch the app with an instance of the UIViewController with a green background.

The REPL lets you type in Ruby code. There is a limitation: methods and constants have to be loaded already before it can be used in the REPL. This means that if your source code doesn't use a certain method, it might not be availabe in the REPL.

While the app is running in the simulator, Cmd+clicking on a view will make the view the current object in the REPL. i.e. self will refer to the view. It's handy for debugging or trying out custom views.

Using External Objective C Code

RubyMotion has great support for reusing Objective C code in the form of Cocoapods (note: you need to add the motion-cocoapods gem first, see below). Just do this in the Rakefile:

  app.pods do
    pod 'Facebook-iOS-SDK', '~> 3.7.1'
    pod 'NSData+Base64'
  end

And run pod setup the first time and subsequently rake pod:install when you update the list of pods used.

You can also vendor Objective C code directly, by doing this in the Rakefile:

  app.vendor_project('vendor/AEImageAttributedString', :static)

Using External RubyMotion Code

You can also include RubyMotion code libraries which are packaged as (Ruby)gems by adding lines like this to your Gemfile:

gem 'motion-cocoapods'
gem 'bubble-wrap', :path => 'vendor/BubbleWrap'

You can either install the gem or specify a path pointing to a subdirectory of your project. To install the gem, run rake install gem_name or preferably run bundle install to install all the gems listed in your Gemfile using Bundler. For the latter, you usually pull and manage the vendored code via a mechanism such as git submodule.

A Few Nice Alternatives in Ruby

if (!something) {
    [self doSomething];
}
unless something #if !something works too
    doSomething
end

In addition to if-statements, there's an unless statement that negates the condition.

(void)doSomethingWithString(NSString* s) {
    if (s == @"SOME DEFAULT") {
        return;
    }

    //do something with s
}
def doSomethingWithString(s)
    return if s == 'SOME DEFAULT'

    #do something with s
end

obj.doSomething unless someCondition

There's a shortcut alternative for if statements, Extremely handy for guard clauses. The unless statement has a similar alternative syntax.

int v = 1
NSLog(@"This is good %d", v);
NSString* s = [NSString stringWithFormat:@"This is good too %d", v];
v = 1
NSLog("This is good #{v}") #or p "This is good #{v}"
s = "This is good too #{v}"
s = 'This is not good #{v}'

Ruby supports string interpolation by using double quotes "". Note that strings enclosed by single quotes '' do not trigger string interpolation.

Grand Central Dispatch

Dispatch::Queue.main.async do
    #Code that runs asynchonously on main thread
end

Dispatch.once {
    #Code that only runs once
}

Using Grand Central Dispatch (GCD) is easy.

def self.instance
    Dispatch.once { @instance = new }
    @instance
end

This create a singleton.

Constants

All Ruby constants start with capital letters. RubyMotion makes constants in Objective C available in RubyMotion with the exact casing unless the constant starts with a lower case:

UIStatusBarStyle style = UIStatusBarStyleDefault;
CFStringRef type = kUTTypeData;
style = UIStatusBarStyleDefault #Verbatim
type = KUTTypeData #Capitalize first letter

Memory Management

RubyMotion implements automatic memory management so there is no need for calling retain and release explicitly. As of now, the implementation is based on the autorelease pool infrastructure. So the similar monitoring of -retainCount and -dealloc in Objective C is helpful for debugging retain cycles and leaks.

Gotchas

nil (an instance of NilClass) in Ruby is not message-eating like nil is in Objective C. So you'll need to add nil checks.

Ruby is a dynamic language and the compiler doesn't check if messages referenced have matching methods, nor if variables exists (since there's no variable declaration and variables are automatically created when once referenced). This means it's possible to call the wrong method (hence getting a NoMethodError) or access the wrong variable (hence getting a nil) via mispelling. The Ruby community has a strong testing culture. RubyMotion includes MacBacon a Mac clone of the RSpec framework.

RubyMotion automatically converts to and fro Objective C primitives (BOOL, int, long, etc) between and their Ruby equivalents. Note that there is no Ruby primitives, so BOOL in Ruby is TrueClass and FalseClass (with singleton instances true and false respectively) and numbers are Fixnum or Bignum. This will usually work nicely behind the scenes, except to note that when BOOL in Objective C is expected, false and nil in RubyMotion converts to NO, and everything else converts to true, including 0 (since it's an object of type Fixnum).

Blocks in RubyMotion automatically retain self so it is easy to create a cycling reference and leak memory when a UIControl subclass stores a block (which retains self, the UIControl) as a event handler. Use a weak reference WeakRef in such cases. This is more often encountered when writing libraries or frameworks rather than in application-level code.

Storyboard and Core Data

You'll need to continue to use Xcode to create and edit them, but .xib, .storyboard and .xcdatamodeld files in the resources directory are automatically included when building.

Testing on Device

rake device will create a development build of the app and upload it to the attached device. rake device debug=1 will build, upload and run the app on the device, attaching the debugger to it

Running motion device-console while an iOS device is tethered with a USB cable tails the device log. Note that this is separate from running the app while using RubyMotion, so you can run motion device-console while running an Ad hoc build or App Store build, for example.

sudo gem install motion-testflight #or bundle install

You can use the motion-testflight gem to submit builds to TestFlight.

Submission

Configure your new app or update in iTunes Connect as per normal. rake archive:distribution will create an .ipa file suitable for submission. You will need to use the Application Loader app bundled with Xcode to submit your app.

Additional Notes

Use the REPL to your advantage. The #methods (accepting a boolean) method is very useful for exploring methods available to an object.

Very Useful Resources

The official RubyMotion documentation is very detailed and a must-read:

  1. RubyMotion Runtime Guide
  2. RubyMotion Project Management Guide
  3. Writing Tests for RubyMotion Apps
  4. RubyMotion Google Group

What's Next

Here's few useful gems to check out and a short 1-liner description of what it does:

Acknowledgments

Thanks to Daphane Khoo for reading drafts of this and the following for suggestions and corrections:

  • Caram
  • Mateus

.