Mixing Objective-C and Ruby
Aug 24, 2012I've written a lot about RubyMotion, but one aspect I haven't touched on is how you can mix-and-match Objective-C and Ruby. That means you can use Objective-C code in RubyMotion projects and use Ruby code in traditional Objective-C apps. That sounds like black-magic, so let's walk through some quick examples.
Objective-C in Ruby
Most iOS developers have a deep backlog of existing code, and porting it to RubyMotion by hand is a pain. Luckily, we can just add the compiled Objective-C to our shiny new RubyMotion apps.
Imagine we have a CYAlert
class we want to use in our RubyMotion app; it's pretty short and looks something like this:
// CYAlert.h
#import <UIKit/UIKit.h>
@interface CYAlert : NSObject
+ (void)show;
@end
// CYAlert.m
#import "CYAlert.h"
@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
To use this in our RubyMotion app, we just put both files inside a folder (such as ./vendor/CYAlert
) and tell RubyMotion to also build that folder using vendor_project
in the Rakefile
:
Motion::Project::App.setup do |app|
# Use `rake config' to see complete project settings.
app.name = 'MixingExample'
# ./vendor/CYAlert contains CYAlert.h and CYAlert.m
app.vendor_project('vendor/CYAlert', :static)
end
Now in our AppDelegate
, we can use our CYAlert
as expected:
class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
CYAlert.show
true
end
end
Pretty neat, huh? RubyMotion knows how to build two types of Objective-C codebases: :static
as shown above and :xcode
, which is exactly what it sounds like. You can read more about the options for both in the RubyMotion docs.
CocoaPods
Since RubyMotion can build any old Xcode project, using the existing CocoaPods system doesn't require a huge leap of imagination. The motion-cocoapods gem adds a nice DSL to automate the process in your Rakefile
, like so:
...
require 'motion-cocoapods'
Motion::Project::App.setup do |app|
# ...
app.pods do
pod 'JSONKit'
end
end
Ruby in Objective-C
Compiling to a common bytecode is a two-way street: we can also bundle Ruby classes into Objective-C projects. That sounds wild, right? Imagine we re-implemented our CYAlert
in Ruby, like so:
# ./app/CYAlert.rb
class CYAlert
def self.show
alert = UIAlertView.alloc.init
alert.title = "This is Ruby"
alert.message = "Mixing and matching!"
alert.addButtonWithTitle "OK"
alert.show
end
end
If we run rake static
, this will compile our project into a static library at ./build/<app name>-universal.a
. We then add this to our Xcode project with the following steps:
- Add static library to project
- Link against additional required libraries
- Initalize the RubyMotion runtime
// main.m
int main(int argc, char *argv[])
{
@autoreleasepool {
void RubyMotionInit(int, char **);
RubyMotionInit(argc, argv);
...
}
}
Now we're cooking with gas. One caveat: RubyMotion does not generate .h
files for us, so we have to manually declare our classes and methods somewhere in Objective-C. It's a good idea to create a header for each class, so we could create a CYAlert.h
like this:
// CYAlert.h
@interface CYAlert : NSObject
+ (void)show;
@end
@implementation CYAlert
// Empty on purpose
@end
And in any of our classes, such as the app delegate, we need only import the header and use to our heart's content:
// ExampleDelegate.m
#import "CYAlert.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
[CYAlert show];
return YES;
}
So if something is painful to write in Objective-C, just write it in Ruby and easily add it to your project. Super neat stuff.
You can find the source for the above examples on Github.