9781449380373
_objective_c_code_in_macruby_apps.html

Chapter 12. Objective-C Code in MacRuby Apps

In the previous chapter, you saw how to embed MacRuby into an Objective-C project. While MacRuby works great for building Cocoa apps, there are some cases where performance and low-level programming is important. In other cases, you may already have a library written in Objective-C and you would like to use it in your MacRuby project.

Dynamic Library

If the Objective-C code you would like to use isn’t in a framework, the easiest way to package it to make use of it in MacRuby is to create a Dynamic Library.

Let’s pretend we already wrote some awesome Objective-C code that we rely on heavily, and we want to share this code between our projects. Following is the code we are going to try to reuse.

Header file—Spelling.h:

#import <Foundation/Foundation.h>

@interface Spelling : NSObject{
  NSDictionary *table;
}

- (Spelling*) initWithBuiltinTable;
- (NSString*) britishize:(NSString*)string;
@end

Implementation file—Spelling.m:

#import "Spelling.h"

@implementation Spelling

- (Spelling*) initWithBuiltinTable {
  self = [super init];
  if ( self ){
    table = [NSDictionary dictionaryWithObjectsAndKeys:
      @"flat", @"apartment",
      @"row", @"argument",
      @"pram", @"baby carriage",
      @"plaster", @"band-aid",
      @"loo", @"bathroom",
      @"tin", @"can",
      @"mince", @"chopped beef",
      @"biscuit", @"cookie",
      @"maize", @"corn",
      @"nappy", @"diaper",
      @"lift", @"elevator",
      @"rubber", @"eraser",
      @"torch", @"flashlight",
      @"chips", @"fries",
      @"petrol", @"gas",
      @"bloke", @"guy",
      @"motorway", @"highway",
      @"bonnet", @"hood",
      @"jelly", @"jello",
      @"jam", @"jelly",
      @"paraffin", @"kerosene",
      @"solicitor", @"lawyer",
      @"number plate", @"license plate",
      @"queue", @"line",
      @"post", @"mail",
      @"caravan", @"motor home",
      @"cinema", @"movie theater",
      @"silencer", @"muffler",
      @"serviette", @"napkin",
      @"nought", @"nothing",
      @"flyover", @"overpass",
      @"dummy", @"pacifier",
      @"trousers", @"pants",
      @"car park", @"parking lot",
      @"full stop", @"period",
      @"chemist", @"pharmacist",
      @"crisps", @"potato chips",
      @"hire", @"rent",
      @"banger", @"sausage",
      @"pavement", @"sidewalk",
      @"football", @"soccer",
      @"jumper", @"sweater",
      @"bin", @"trash can",
      @"lorry", @"truck",
      @"boot", @"trunk",
      @"holiday", @"vacation",
      @"waistcoat", @"vest",
      @"windscreen", @"windshield",
       nil ];
  }
  return self;
}

- (NSString*)britishize:(NSString*)string{
  NSString* conversion = [table objectForKey:string];
  if( conversion ){
    return conversion;
  }else{
    return string;
  };
}

@end

This code implements a class called Spelling with an instance method called britishize that takes a string and, if a translation is found, returns the British equivalent of the string. Otherwise, the passed string is returned.

This is how we can use our code in an Objective-C project:

Spelling* converter = [[Spelling alloc] initWithBuiltinTable];
NSString* result = [converter britishize:@"truck"];
NSLog(@"%@\n", result);
# => 2011-05-30 14:44:09.293 example[73135:903] lorry

Obviously, this is just a trivial example to illustrate this concept. For those who don’t know Objective-C but are familiar with Ruby, the equivalent code written in Ruby looks more or less like this:

module Spelling

  @table = Hash["apartment", "flat",
                "argument", "row",
                "baby carriage", "pram",
                "band-aid", "plaster",
                "bathroom", "loo",
                "can", "tin",
                "chopped beef", "mince",
                "cookie", "biscuit",
                "corn", "maize",
                "diaper", "nappy",
                "elevator", "lift",
                "eraser", "rubber",
                "flashlight", "torch",
                "fries", "chips",
                "gas", "petrol",
                "guy", "bloke, chap",
                "highway", "motorway",
                "hood", "bonnet",
                "jello", "jelly",
                "jelly", "jam",
                "kerosene", "paraffin",
                "lawyer", "solicitor",
                "license plate", "number plate",
                "line", "queue",
                "mail", "post",
                "motor home", "caravan",
                "movie theater", "cinema",
                "muffler", "silencer",
                "napkin", "serviette",
                "nothing", "nought",
                "overpass", "flyover",
                "pacifier", "dummy",
                "pants", "trousers",
                "parking lot", "car park",
                "period", "full stop",
                "pharmacist", "chemist",
                "potato chips", "crisps",
                "rent", "hire",
                "sausage", "banger",
                "sidewalk", "pavement",
                "soccer", "football",
                "sweater", "jumper",
                "trash can", "bin",
                "truck", "lorry",
                "trunk", "boot",
                "vacation", "holiday",
                "vest", "waistcoat",
                "windshield", "windscreen"]

  def self.britishize(str)
    @table[str] || str
  end

end

To share our Objective-C code, we need to create a Dynamic Library. Create a new Xcode project and choose the Cocoa Library template in the Framework & Library category (Figure 12.1, “Cocoa Library template selection”).

Figure 12.1. Cocoa Library template selection

Cocoa Library template selection

Call the project Spelling and make sure the type value is set to Dynamic. Once the project has generated, we need to make some build settings changes to our main target.

The first change is to the compiler settings so garbage collection is supported (Figure 12.2, “Setting the GC options”).

Figure 12.2. Setting the GC options

Setting the GC options

Note

I noticed a weird bug in some versions of Xcode, where some compiler settings appear only after I tried to build a project. If you don’t see the Code Generation settings, try to build your current project and go back to the Build Settings.

Then we need to change a Packaging setting so the Executable Extension is bundle instead of dylib (Figure 12.3, “Changing the file extension name”).

Figure 12.3. Changing the file extension name

Changing the file extension name

Now that our build is ready, we can add the two Objective-C files shown earlier. You can type them or copy them from the book’s GitHub repository.

Once you have added the files to the project, we need to do one more thing: add an empty function to Spelling.m. Put the following code before the class implementation:

void Init_Spelling(void) { }

We defined a function with a name such as Init_xxx, where xxx is the name of our Dynamic Library. When requiring our Dynamic Library, MacRuby will automatically call this method. If it doesn’t exist, MacRuby will throw an exception. This might be weird, but it’s actually related to the way C extensions work in Ruby.

Copy the output file to a new location and try the following from the command line:

$ macirb --simple-prompt
>> require 'Spelling'
=> true
>> converter = Spelling.alloc.initWithBuiltinTable
=> #<Spelling:0x2000f8d00>
>> converter.britishize("truck")
=> "lorry"

As you can see, the Dynamic Library acts as if it was a Ruby file, and you can use it directly. It’s a very convenient way to use Objective-C code in your MacRuby projects.

There are two caveats with using Dynamic Libraries. One is due to MacRuby relying on the GC. All your code needs to be GC-compatible and must be compiled with the appropriate GC flags. The other is that this approach doesn’t support BridgeSupport files, so if you use nonobject-oriented items, this approach won’t work.

Framework

If you are trying to use a third-party Cocoa library, it will probably be packaged as Cocoa frameworks. Cocoa frameworks are the most common format to share Objective-C code. MacRuby users also should consider using this format if they need to use BridgeSupport (see the section called “Using Objective-C or C Code”) files. In this section, you’ll see what is needed for a framework to be used by MacRuby and how to ship an application with third-party frameworks.

One of the most popular third-party frameworks is called Sparkle, and it is available on GitHub. Sparkle is a software update framework. It’s a nice tool that allows you to add built-in update notifications and downloads from within your app. Unfortunately, the App Store policy currently doesn’t allow developers to use this framework, because all updates have to go through the App Store. Nonetheless, we are going to use it as an example for this section.

Clone or download the source code and open Sparkle.xcodeproj in Xcode. If you look at the Objective-C Garbage Collection section in the build settings, you will see that the GC is marked as supported, so we don’t need to change anything. Build the framework and make sure the process goes through properly. At the time of this writing, the current code links against the OS X 10.5 SDK, so you might have to change the base SDK setting.

Note

You can also download the precompiled framework from the Sparkle website.

Once the framework is built, we need to add it to our project. However, we want the application to dynamically link against the framework at runtime. Otherwise, Xcode will link against a hardcoded path when the app is compiled and our application will work only on our own machines or on the machines of users who have the framework in exactly the same path. To do the dynamic linking, we will add the framework to the source files and edit the target to copy it to the Frameworks folder inside the .app.

Now we’re ready to build a small MacRuby sample app using Sparkle.

Start Xcode and create a new MacRuby app called MyApp (leave all the template options off). Now drag and drop the Sparkle framework we built earlier into the supporting files folder (Figure 12.4, “The Sparkle framework added to our project”). When asked, make sure you choose not to add the file to any targets, but select the option to copy the file. The only point of this step is to give Xcode a reference to the framework.

Figure 12.4. The Sparkle framework added to our project

The Sparkle framework added to our project

Now, add a target’s build phases by selecting the project name in the Project Navigator, clicking the Build Phases tab, and clicking the Add Build Phase button (Figure 12.5, “Adding a new build phase”).

Figure 12.5. Adding a new build phase

Adding a new build phase

Select the Add Copy Files option. A new entry should appear above. Expand the menu, and change the destination to Frameworks. Finally, drag and drop the Sparkle.framework file to the area labeled Add files here (Figure 12.6, “Sparkle added to the private frameworks in the copy phase”).

Figure 12.6. Sparkle added to the private frameworks in the copy phase

Sparkle added to the private frameworks in the copy phase

To test your setup, you can build the app and look at the .app produced. The easiest way to do that is to expand the Products section in the Project Navigator, right-click MyApp.app, and select show in Finder. To inspect our application structure, in Finder, right-click the file and select Show Package Contents. Expand the Contents folder and you should see a Frameworks folder containing the Sparkle framework.

The last thing we need to do is load the framework when our code loads. Going back to Xcode, edit the rb_main.rb file and add the following line after the Cocoa framework loading line:

framework 'Sparkle'

The framework is now available in your code and you can make good use of it. You will find a lot of documentation on the author’s website, showing how to set your application to use Sparkle.

BridgeSupport

There are some cases where the framework you want to use relies on nonobject-oriented items such as constants, enumerations, structures, and functions. This is the case for the PS3SixAxis framework, available at the PS3SixAxis GitHub site.

This framework lets you connect a PS3 controller and interact with it via USB or Bluetooth. Part of the framework is written in C and, because of that, MacRuby can’t introspect all the code at runtime. To work around this problem, you or the framework author needs to generate BridgeSupport files and add them to the framework itself.

Generating BridgeSupport is covered in the section called “Using Objective-C or C Code”, but let’s take this framework example to generate new BridgeSupport files.

Start by cloning or downloading the PS3SixAxis repository. I will put the files in ~/tmp, but feel free to put them anywhere you want:

$ mkdir -p ~/tmp
$ cd ~/tmp
$ git clone git@github.com:mattetti/PS3SixAxis.git
Cloning into PS3SixAxis...
remote: Counting objects: 85, done.
remote: Compressing objects: 100% (54/54), done.
remote: Total 85 (delta 26), reused 85 (delta 26)
Receiving objects: 100% (85/85), 251.83 KiB | 279 KiB/s, done.
Resolving deltas: 100% (26/26), done.
$ cd PS3SixAxis
$ open PS3_SixAxis.xcodeproj

Build the framework and, to make things easy, copy the framework file to ~/tmp:

$ mkdir ~/tmp/PS3Controller.framework/Resources/BridgeSupport/
$ gen_bridge_metadata --64-bit -f ~/tmp/PS3Controller.framework/
-o ~/tmp/PS3Controller.framework/Resources/BridgeSupport/PS3Controller
.bridgesupport

The framework is now ready to be consumed by a MacRuby app. As you can see, to generate the BridgeSupport files, we first created a BridgeSupport folder inside the Resources folder and used the gen_bridge_metadata command with the 64-bit and framework flags and stored the output in the newly created folder.

Site last updated on: November 9, 2011 at 10:00:57 AM PST