9781449380373
_one_step_deeper.html

Chapter 8. One Step Deeper

In Chapter 7, Core Data, we looked at the basics of MacRuby. You are now ready to get started with developing complex apps. Depending on the way you approach learning, you might want to first jump to hacking or you might want to first learn more advanced techniques. This chapter will focus on some advanced aspects of MacRuby that will help you go deeper in your MacRuby knowledge. Feel free to skip this chapter and come back to it later on if that makes more sense to you.

Selectors

In Objective-C, methods are identified by selectors. A selector is composed of the name of the method and keywords specifying the arguments to the method. If you have two methods that are the same except for the number and/or kind of arguments they accept, Objective-C treats them as separate methods. In Ruby, a method is specified by its name alone. The sort of arguments passed to a method have no bearing on which Ruby method is called. Therefore, to support the Objective-C method calling style, MacRuby extends Ruby to create Objective-C selectors from the arguments passed to a function. The examples in this chapter are meant to explain MacRuby’s method overloading, (see the Wikipedia entry for method overloading at http://en.wikipedia.org/wiki/Method_overloading), which is not something Ruby programmers normally deal with. Look carefully at the examples. The method names are always the same, and only the expected parameters are different.

Let’s imagine that we have a Player class and that it defines a method called add_contact that takes two parameters: a name and a hash with a tags key. This method adds a new contact to the player contact list, tagging the new contact with the passed tags. We can also define another method called add_contact that takes two different parameters: a name and a hash with the location key:

class Player
  def add_contact(name, tags:contact_tags)
    @name = name
    @tags = contact_tags
    "new contact #{name} was added and tagged."
  end

  def add_contact(name, location:contact_location)
    @name = name
    @contact_location = contact_location
    "new contact #{name} was added and located."
  end
end

There are four different ways to call the add_contact method:

player = Player.new
player.add_contact('Matt', tags: ['Ruby', 'Cocoa'])  
# => new contact Matt was added and tagged.
player.add_contact('Matt', location: 'San Diego')    
# => new contact Matt was added and located.
player.add_contact('Matt')                           
# => NoMethodError: undefined method 'add_contact'
player.add_contact('Matt', tags: ['Ruby'], location: 'San Diego') # => NoMethodError:

As you can see, the method signature/selector used will determine which method to dispatch. If we want to define a method for the last two examples, we can use the following:

class Player
  def add_contact(name, options={})
    # do something with the options passed
  end
end

This is important to understand, because you will often need to use selectors in MacRuby when your code relies on Cocoa frameworks.

To help you interpret existing Cocoa documentation for use with Ruby methods, here is an extract of the NSMutableString documentation:

This is how you call this method using an Objective-C selector (in this case, atIndex):

"Ruby".insertString("Mac", atIndex:0)  # => "MacRuby"

Note

You can get the same results by calling an entirely different method. The Ruby insert method produces the same result if used as follows: "Ruby".insert(0, "Mac")

Hopefully this example helps you understand how to use the Cocoa documentation to port and implement Objective-C selectors, especially in the case of delegates.

If you remember the “Hello, World!” example we wrote at the beginning of this chapter, you might remember that we passed a selector to the button action. The selector was a string with the method name, followed by a colon (the colon was needed because the method had arguments). You might wonder why we called it a selector, whereas in this section, selectors seem different.

The source of confusion is that in Objective-C, selector has two meanings. It can refer to a method signature or the unique identifier associated with a method. By the first definition, methods that take different arguments have different selectors, whereas, by the second, all methods with the same name have the same selector.

Blocks

Another important part of the Ruby syntax is the concept of blocks. MacRuby has blocks as well as procs and lambdas. They are all commonly referred as closures. Let’s look at some example code to understand this powerful concept:

days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
days.each_with_index do |day, index|
  puts "day #{index + 1} of the week is #{day}"
end

Running the code above will result in the following output:

day 1 of the week is Sunday
day 2 of the week is Monday
day 3 of the week is Tuesday
day 4 of the week is Wednesday
day 5 of the week is Thursday
day 6 of the week is Friday
day 7 of the week is Saturday

days is an array on which we are calling the each_with_index method and passing it a block. A block is like the body of an anonymous method that is being passed to a defined method as a parameter. The block is invoked as the defined method executes.

In this case, the block is delimited by the do/end keywords, but you can also define blocks using curly brackets, as shown here:

days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
days.each_with_index{ |day, index|  puts "day #{index + 1} => #{day}" }

This code will generate the following output:

day 1 => Sunday
day 2 => Monday
day 3 => Tuesday
day 4 => Wednesday
day 5 => Thursday
day 6 => Friday
day 7 => Saturday

each and each_with_index are iterators—methods that invoke a block of code for each of its contained items. In the case of each_with_index, two arguments are passed to the block on each iteration: an array item’s value and its index. The block called in the previous example prints out a string, interpolating the two passed values.

Note

There are a few different ways to use closures in MacRuby, but the examples shown in this section are the most common usage you will encounter.

When using blocks, you need to be careful about the scope you are operating on. When called, a block has access to the context it’s being called on and will operate on it. Here is a simple example:

macruby_genius = 'Laurent'
developers = ['laurent', 'josh', 'matt', 'jordan']
developers = developers.map do |dev|
  macruby_genius = dev
end
macruby_genius # => "jordan"

As you can see, each iteration of the array redefines the macruby_genius variable defined earlier. So the value assigned last remains as part of the surrounding context after the do loop ends. A block is different from a method, which implicitly creates its own local context, as shown in the following example:

macruby_genius = 'Laurent'
developers = ['laurent', 'josh', 'matt', 'jordan']
def developer_names(devs)
  devs.map do |dev|
    macruby_genius = dev
  end
end
developer_names(developers)
macruby_genius # => "Laurent"

In this case, the macruby_genius variable exists in two different contexts. Therefore, even though it is being redefined in each iteration within the developer_names method, the variable set outside of the method is not affected.

Blocks can make your code easier to read and maintain. Let’s say you want to create five new Contact instances and add them to an array.

In a different language, you might do something like this:

Contact = function(){};
contacts = [];
for (i = 0; i < 5; i++) {
  var contact = new Contact;
  contacts[i] = contact;
}

However, with MacRuby, you can just write:

# Dummy objects referenced in the example
class Contact; end

# same code as the javascript example above:
contacts = []
5.times do
  contacts << Contact.new
end

# Or you can use this shortened syntax
contacts = Array.new(5){ Contact.new }

both of which, at least in my mind, read much better than the version without blocks.

Since MacRuby 0.7.1, Objective-C APIs using C blocks are supported. Here is a simple example using Array#enumerateObjectsUsingBlock (see http://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html#//apple_ref/doc/uid/20000137-SW19):

framework 'Foundation'
array = [1, 2, 3, 4, 5]
proc = Proc.new do |obj, index, stop|
  p obj
  stop.assign(true) if index == 2
end
array.enumerateObjectsUsingBlock(proc)

Here is the output:

1
2
3
=> [1, 2, 3, 4, 5]

You might be surprised that we changed the value of stop by using #assign. The reason we cannot just use the = operator is that the Objective-C API defines stop as a pointer to a boolean value, so we have to reassign the pointer’s value to make the loop stop. Read the section called “Pointers” for more information about using pointers with MacRuby.

Concurrency

CPU clock speeds aren’t increasing as quickly as they used to. Instead, to scale programs and improve performance, chipset designers have to add more cores to the processors. At the same time, users expect programs to do more complicated things and to do them faster than before.

The obvious solution for a long time was to use threads. The problem with threads is that they require a lot of development attention, and if not handled properly, they can seriously affect the system performance and embed difficult-to-find bugs in programs. Threads are available, and are discussed further in this book. However, since the Snow Leopard release, Apple offers a very interesting API called Grand Central Dispatch (GCD). GCD is an API on top of the libdispatch library, which Apple open sourced.

Grand Central Dispatch

MacRuby developers have full access to GCD. Unless you have developed in Objective-C in the past, you probably are not familiar with this library. This section gives you a quick overview of the key APIs. For more information about GCD, refer to Apple’s documentation (http://developer.apple.com/library/ios/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html).

Queues

GCD provides and manages dispatch queues, which allow developers to execute blocks of code on a pool of threads fully managed by the system (the pool grows and shrinks dynamically and distributes its threads evenly among available processors). You can think of a queue as a pool of workers ready to take orders. You don’t have to worry about how they will share the workload among themselves. You pick a queue based on a few key criteria—such as whether they tackle tasks serially or concurrently—and then send the queue instructions about executing your task.

Queues in MacRuby are handled by the Dispatch::Queue class. GCD has three types of queues:

Main queue

Tasks are executed serially on your application’s main thread. The main queue is automatically created by the system.

Concurrent queue

Tasks start executing in the order in which you queue them, but can run concurrently. Use these queues when you want to execute large numbers of tasks concurrently. Three concurrent queues are automatically created for your application: one that runs at a high priority, one that runs at a low priority, and the default queue, which lies between them in priority.

Serial queue

Tasks execute one at a time in first in, first out (FIFO) order. Use this queue type when you want to ensure that tasks are executed in a predictable order. Serial queues have to be created by the programmer.

Queues are interesting for a few reasons, mainly because they are lighter (smaller memory footprint, faster allocation) than threads and because developers can rely on the system to manage them. In practice, this means that instead of worrying about the amount of threads to start and manage, you can start as many queues and tasks as you want and not worry about the nitty-gritty details.

To create a new queue, create an instance of Dispatch::Queue and give it a name:

queue = Dispatch::Queue.new('com.oreilly.guide')

Note

The default dispatch queues are serial queues. You can create as many serial queues as you want, and they will run in parallel with each other. But if you want to execute many tasks at the same time, it is more efficient to use a concurrent queue.

Once you have a queue instance, you can dispatch tasks to it using Ruby blocks (see the section “Blocks” as a reminder). Tasks can be dispatched synchronously or asynchronously, as shown here:

queue = Dispatch::Queue.new('com.oreilly.guide')
queue.sync do
  puts 'Blocking operation...'
  sleep(3)
  puts 'done waiting!'
end
puts 'Synchronous dispatching evaluated'

queue.async do
  puts "Async call..."
  sleep(3)
  puts 'Async block done'
end
puts 'Asynchronous dispatching evaluated'

If you run this code, you will notice that the first call using #sync is a blocking call and the rest of the code won’t be executed until after the block is done running. The other call, using #async, isn’t blocking and will be executed concurrently because it is being processed in the background.

Using the synchronous approach ensures the data is accessed by only one task at a time, making the use of a mutex redundant while keeping the code simple. Here is an implementation example showing how a missing launcher class can ensure its instances fire in the proper order, one after the other, respecting the chronological order:

class MissileLauncher
  def initialize
    @queue = Dispatch::Queue.new('org.macruby.synchronizer')
  end
  def launch!
    # Because serial queues execute their tasks in FIFO order, we can be sure
    # that only one thread will be running the passed block at any given time.
    @queue.sync do
      # critical section: here we arm the missiles and fire them.
      puts "BOOM!"
    end
  end
end

If you don’t need to run your tasks in a given order, you can use the concurrent queues:

Dispatch::Queue.concurrent(:default).async do
  # expensive calculation run concurrently
end
# which is the same as
Dispatch::Queue.concurrent.async do
  # expensive calculation run concurrently
end
# Other concurrent queues can be used calling them using
# their priority level:
Dispatch::Queue.concurrent(:low)
Dispatch::Queue.concurrent(:high)

Note

The main queue isn’t covered in detail here, since it’s not useful for concurrency. However, it should never be used for long-running tasks, because it keeps the UI from interacting with the user.

Groups

GCD groups are designed to make task synchronization trivial. In some cases, you need to ensure a queue has executed all of its tasks before doing something else. After you create a group, you can assign tasks to it as you dispatch them and make your code wait for all the group’s tasks to be executed. Alternatively, you can register a block to be triggered when the group’s tasks have all executed.

Here is the MacRuby implementation of the concept of Futures, found in the Io language. Basically, the concept is simple: create a proxy object performing expensive computations in the background and then collect the results:

class Future
  def initialize(&block)
    # Each thread gets its own FIFO queue upon which we will dispatch
    # the delayed computation passed in the &block variable.
    Thread.current[:futures] ||=
      Dispatch::Queue.new("org.macruby.futures-#{Thread.current.object_id}")
    @group = Dispatch::Group.new
    # Asynchronously dispatch the future to the thread-local queue.
    Thread.current[:futures].async(@group) { @value = block.call }
  end
  def value
    # Wait for the computation to finish (if not already done)
    @group.wait
    # then just return the value in question.
    @value
  end
end

Use the following to create a future and fetch the result of the calculation:

result = Future.new do
  p 'Engaging delayed computation!'
  sleep 2.5
  42 # Your result would go here.
end

# Then to fetch the calculation result:
result.value # => 42

The important aspect of this implementation is that we are defining a queue and a group. We are then dispatching blocks to the group. Before checking the value, we are first verifying that all the tasks have executed. It’s common practice to group concurrent tasks inside a serial queue to process groups of tasks in a sequential order.

The following example shows how to speed up Array#map by spreading out the total work across the system’s available processors:

class Array
  def parallel_map(&block)
    result = []
    # Creating a group to synchronize block execution.
    group = Dispatch::Group.new
    # We will access the `result` array from within this serial queue,
    # as without a GIL (Global Interpreter Lock) we cannot assume array access 
    # to be thread-safe.
    result_queue = Dispatch::Queue.new('access-queue.#{result.object_id}')
    0.upto(self.size) do |idx|
      # Dispatch a task to the default concurrent queue.
      Dispatch::Queue.concurrent.async(group) do
        temp = block[self[idx]]
        result_queue.async(group) { result[idx] = temp }
      end
    end
    # Wait for all the blocks to finish.
    group.wait
    result
  end
end

Note

MacRuby doesn’t use a Global Interpreter Lock (a lock preventing multiple threads to access the same data at the same time), which means that it is truly concurrent. You can read more about Ruby and concurrency in an article from my blog at http://merbist.com/2011/02/22/concurrency-in-ruby-explained.

GCD also offers other features, such as interthread communication and resource management, as well as a low-level foundation for event-based programming. But these features fall outside of the scope of this section.

GCD dispatch gem

While MacRuby has native full access to the GCD APIs, in some cases, developers might wish to use more abstraction and get some extra features. This is what the GCD dispatch gem offers.

Like all Ruby gems, the dispatch gem is hosted on Rubygems.org.

To install the gem, enter the following:

$ macgem install dispatch

Note

MacRuby lets you bundle gems with your apps when you compile it.

Here is a quick tour of what is available to you:

require 'dispatch'
job = Dispatch::Job.new { slow_operation }
job.value # => “wait for the result”

As you can see, the API looks very similar to the Thread API. The difference is that the "Dispatch::Job" is in reality a wrapper around GCD’s default concurrent queue. When you add a new block to the queue, the main thread doesn’t stall.

To retrieve the result of the passed block, you have two options. The one shown above, calling #value on a job, will block the current thread until the job is processed. The other option is to use the asynchronous API, as follows:

require 'dispatch'
job = Dispatch::Job.new { slow_operation }
job.value do |v|
  "operation result: #{v} - asynchronous dispatched!"
end

In this case, the result of the job processing will be triggered asynchronously and the result will be passed to the block.

But that’s not all. You can send a bunch of operations to a job and let GCD decide how many threads should be used underneath—all of that is totally transparent to you:

require 'dispatch'
job = Dispatch::Job.new
job.add { slow_operation(a) }
job.add { slow_operation(b) }
job.add { slow_operation(c) }
job.join
job.values # =>[result_a, result_b, result_c]

Calling values in this case returns all the returned operation results. However, this is not a blocking call, so to make sure all the operations are done processing, the caller calls #join.

The only challenge with this approach is that because in MacRuby each thread runs in its own nonlocking, reentrant VM, there is a risk of destroying data integrity. Two operations might try to access the same data at the same time, corrupting the data.

Luckily, MacRuby comes with another wrapper around GCD’s serial queues, providing lock-free synchronization. Simply speaking, the developer defines a proxy object that has a private access queue, making sure it keeps its data integrity:

require 'dispatch'
job = Dispatch::Job.new
@scores = job.synchronize Hash.new
[user1, user2, user3].each do |user|
  job.add{ @scores[user.name] = calculate(user) }
end
job.join
@scores[user1.name] # => 1042
@scores[user2.name] # => 673
@scores[user3.name] # => 845

MacRuby’s wrapper even goes further and provides a concurrent alternative version of most of the Ruby enumerable methods. What I mean by that is that by using the parallel version of an enumerable method, we let GCD run each iteration concurrently. Consider the following code:

start = Time.now
25.times do
  sleep(1)
end
puts "Took #{Time.now - start} seconds"
# => Took 25.011652 seconds

As expected, the code took a bit more than 25 seconds, since we asked it to sleep 25 times for 1 second.

Now, let’s use the concurrent version provided by GCD:

require 'dispatch'
start = Time.now
25.p_times do
  sleep(1)
end
puts "Took #{Time.now - start} seconds"
# => Took 13.028822 seconds

Instead of calling #times, we called #p_times, which is the parallel/concurrent version of #times. In the background, GCD figures out the optimal number of threads to use and runs the iterations in parallel.

This example should let you imagine how you can optimize the performance of your code by running your iterations in parallel:

require 'dispatch'
locations = ["San Diego", "Chicago", "New Orleans", "Paris", "Singapore"]
locations.p_each do |location|
  # expensive calculation
  location.update_stats
end

Note

The concurrent enumerable methods are synchronous, meaning they won’t return until all the iterations have completed. This way, the APIs stay close to their nonconcurrent counterparts.

To see the available concurrent enumerable methods you can use this trick:

$ macruby -rdispatch -e "puts [].methods.grep(/^p_/)"
# => p_each
# => p_each_with_index
# => p_map
# => p_mapreduce
# => p_find_all
# => p_find

To get the list, I just asked MacRuby to evaluate a string after requiring the dispatch library. Call the macruby binary with the --help flag to see the various options. Because we checked on the enumerable methods, p_times wasn’t displayed, because it is defined on the Integer class.

The last GCD wrapper feature that I would like to introduce is the MapReduce implementation (see the Wikipedia entry for MapReduce at http://en.wikipedia.org/wiki/MapReduce). MapReduce allows you to process large amounts of data in parallel and compute the results. Let’s look at some examples to understand better the usefulness of this concept:

require 'dispatch'
map_reduce = (0..1_000_000).p_mapreduce(0){|n| Math.sqrt(n)}
map_reduce # => 666667166.393145

This example iterates through all numbers from 0 to one million, calculates the square root of each, and ends up by adding all the results together. The addition is implicit and you can specify another operator if you wish to do so.

Ruby developers might see a similarity with the #inject method, except that in p_mapreduce, you have to define a starting value (0 in this example):

require 'dispatch'
map_reduce = (0..10_000).p_mapreduce([], :<<) do |n|
  Math.sqrt(n)
end

This code is the same as the following, except that the following runs iterations consecutively instead of concurrently:

map_reduce = (0..10_000).inject([]) do |sum, n|
  sum << Math.sqrt(n)
end

Code parallelization is difficult, and merely running iterations concurrently doesn’t necessarily mean you will see better results. One of the rules of parallelization is to parallelize only costly code. As a matter of fact, based on that recommendation, the square root examples shown earlier don’t make a lot of sense. Let’s write a real example and see what kind of performance improvement GCD has to offer on my two-core machine:

require 'benchmark'

# Monte Carlo method to find an estimated version of Pi
# For more info, read: http://en.wikipedia.org/wiki/Monte_Carlo_method
def estimated_pi(repetitions)
  inside = 0
  repetitions.times do
    inside += 1 if (Math.sqrt(rand**2+rand**2) < 1)
  end
  ((4*inside))/repetitions.to_f
end

n = 50_000
Benchmark.bm(7) do |x|
  x.report("times:")   { n.times do estimated_pi(200) end }
  x.report("p_times:") { n.p_times do estimated_pi(200) end }
end

This code generates the following output:

             user     system      total        real
times:   6.030000   0.010000   6.040000 (  6.149780)
p_times:  6.300000   0.020000   6.320000 (  3.576102)

Technically, the code takes the same amount of time to execute when you use #times and when you use #p_times. The user, system, and total times are therefore pretty similar, and in fact, the system time for the parallel version is worse (probably because of the overhead of task management). However, the real time reported is half the duration when using the concurrent method. Because the iterations run in parallel on the two cores, the same amount of code can be processed much faster.

GCD offers many more low-level features, such as custom queues, semaphores, and asynchronous events based on sources (timers, signals, and file descriptors, which can be files or sockets). More information is available in the wrapper’s readme file.

Sandboxing

OS X ships with a security facility called sandbox. Sandbox restricts a process’s access to resources. For instance, you can sandbox your application so it doesn’t access the Internet or the local network, or can’t read or write to disk. The sandboxing applies to your entire application, including C extensions or Cocoa APIs you might be using. By using the sandbox facility in your application, you limit potential damage that can happen if a vulnerability in your application is exploited.

MacRuby exposes the Sandbox class to make easy use of the facility. The class comes with five sandboxing profiles:

Sandbox.pure_computation

All operating system services are prohibited.

Sandbox.no_internet

TCP/IP networking is prohibited.

Sandbox.no_network

All socket-based networking is prohibited.

Sandbox.temporary_writes

File system writes are restricted to temporary folders.

Sandbox.no_writes

File system writes are prohibited.

You can also define your own profiles and load them. Check the API for more information.

To apply a sandbox profile, you just need to call #apply! on the profile. Here is an example:

require 'open-uri'
begin
  Sandbox.no_internet.apply!
  open('http://www.macruby.org')
rescue SystemCallError => exception
  puts exception
end

If you execute the preceding code, you will get the following output: Operation not permitted - connect(2).

Warning

If you acquire resources before initiating the sandbox, these resources won’t be controlled by the sandbox.

Using Objective-C or C Code

Because MacRuby runs inside the Objective-C runtime, you can directly call Objective-C and C code using the Ruby syntax. However, some API symbols of frameworks or libraries cannot be introspected at runtime, usually because they are ANSI C symbols denoting nonobject-oriented items such as constants, enumerations, structures, and functions. Apple has provided Objective-C bridges and languages with a way to access these symbols nonetheless. This tool is called the Objective-C Bridges Metadata Generator, or BridgeSupport for short. Apple started shipping BridgeSupport in Mac OS 10.5 Leopard. The project is open source and you can download the latest version or the source code directly from macosforge.org.

Warning

BridgeSupport comes with pregenerated files for all system frameworks. However, the version shipped before Mac OS 10.7 Lion had some bugs. It’s recommended that you use the latest version of BridgeSupport and embed newly generated files with your project.

BridgeSupport comes with XML files describing the C symbols for the system frameworks. But what if you want to use your own Objective-C/C code or a third-party framework or library? It’s actually very simple. BridgeSupport ships with a command-line tool that allows you to create XML files for your symbols.

Let’s pretend I would like to use a framework called FooBar and that this framework uses constants, so I can’t access them from MacRuby directly. The easiest way to make the framework compatible is to generate a BridgeSupport file and to add it to the framework as follows:

$ gen_bridge_metadata --64-bit -f ~/Desktop/FooBar.framework/
        -o ~/Desktop/FooBar.framework/Resources/BridgeSupport/FooBar.bridgesupport

You can see all the options by entering the following:

$ gen_bridge_metadata --help

In this example, we use the --64-bit flag to generate 64-bit annotations and the -f (framework) flag to specify that we are generating a support file for a framework. Finally, we use the -o (output) flag to save the file directly in the framework.

The BridgeSupport folder might not exist inside your framework. No big deal—just make sure to create it before generating the support file:

$ mkdir -p ~/Desktop/FooBar.framework/Resources/BridgeSupport

A BridgeSupport can be installed in one of the following locations:

  • /Library/Frameworks/MyFramework/Resources/BridgeSupport

  • /Library/BridgeSupport

  • ~/Library/BridgeSupport

  • /System/Library/BridgeSupport

For more information about BridgeSupport, check out the manual page by entering the following:

$ man gen_bridge_metadata

Warning

Ruby methods starting with an uppercase letter must be called with explicit parentheses so they don’t get confused with a constant. C functions usually start with an uppercase letter, and therefore you need to make sure to use parentheses even if no arguments are needed.

Scriptable Applications

Mac OS X version 10.5 and later ship with a framework called Scripting Bridge. This framework allows developers to communicate with scriptable applications directly from within their code. Before Mac OS X version 10.5, developers had to use AppleScript scripts to send and handle Apple events. Using this new framework, MacRuby developers can interact directly with applications such as iChat, iTunes, iPhoto, iCal, Keynote, and so on.

A OS X application is said to be scriptable when it exposes a dictionary of Apple events. These events are hooks into the application. The easiest way to see which applications are scriptable and what APIs they expose is to use the AppleScript Editor application. Open AppleScript Editor and click File Open Dictionary. Choose iTunes and browse the API (Figure 8.1, “The AppleScript Editor”).

Figure 8.1. The AppleScript Editor

The AppleScript Editor

OS X also has a series of command-line tools to inspect scriptable applications and show the available methods. The primary tool is called sdef (scripting definition extractor), which extracts the scripting definition of an application. You can give it a try in your terminal by entering the following:

$ /usr/bin/sdef /Applications/iTunes.app

The result is a long XML file describing an Apple event dictionary. The challenge is that some of these definitions use nonobject-oriented items such as constants and enumerations. So what you need to do next is create a BridgeSupport file (the section called “Using Objective-C or C Code”) to make sure you can use the full API from MacRuby.

Note

These steps are not necessary if the API doesn’t use nonobject-oriented items, but to be safe, we are still going through the entire process.

To create a BridgeSupport file, use the sdp command. sdp is an sdef processor:

$ sdef /Applications/iTunes.app | sdp -fh --basename iTunes

Note

sdef, sdp, gen_bridge_metadata, and other tools are located in /usr/bin, which should be in your path.

The previous command generates a scripting definition for iTunes and pipes it to the scripting processor. The processor generates a Scripting Bridge Objective-C header and sets the base name of the generated header file to iTunes. The result is a file called iTunes.h that we can convert into a BridgeSupport file using gen_bridge_metadata:

$ gen_bridge_metadata -c '-I.' iTunes.h > iTunes.bridgesupport

Now we have a BridgeSupport file and are ready to start playing with the Scripting Bridge framework to control iTunes. Turn off iTunes, write the following code in a file, and execute it from the command line:

framework 'Foundation'
framework 'ScriptingBridge'

itunes = SBApplication.applicationWithBundleIdentifier("com.apple.itunes")
load_bridge_support_file 'iTunes.bridgesupport'
itunes.run
itunes.playpause

If everything goes well, iTunes is initialized and starts playing.

At home, I have a MacMini in my living room. In my bedroom, I set up a pair of speakers connected via an AirPort Express. What’s nice with this setup is that I can stream music from my MacMini to my speakers and even control what music is played via my iPhone.

Using MacRuby, I decided to write a small script that would start iTunes and play a special playlist every morning to wake me up slowly. Here is the script in question:

#!/usr/local/bin/macruby
framework 'Foundation'
framework 'ScriptingBridge'

itunes = SBApplication.applicationWithBundleIdentifier("com.apple.itunes")
load_bridge_support_file 'iTunes.bridgesupport'
itunes.run
itunes.stop
library = itunes.sources.find{|source| source.name == 'Library'}
playlist = library.userPlaylists.find do |playlist|
  playlist.name == 'morning'
end
playlist.playOnce(false) if playlist

The script is pretty straightforward. After starting iTunes (if it is not already started) and stopping any item already playing, the script finds the music library and then finds the playlist named morning. If the playlist is found, it is then played.

The AppleScript Editor doesn’t always show all the different available methods, and the documentation might not reflect the options you need. The best way to introspect the available classes is to read the header file or use the interactive command:

$ macirb --simple-prompt
>> framework 'Foundation'
=> true
>> framework 'ScriptingBridge'
=> true
>> itunes = SBApplication.applicationWithBundleIdentifier("com.apple.itunes")
=> #<ITunesApplication:0x200c5bce0>
> (itunes.methods(true, true) - Object.new.methods(true, true)).sort
=> [:EQEnabled, :EQPresets, :EQWindows, :activate, :"add:to:", :backTrack,
 :browserWindows, :"childWithClass:code:keyForm:keyData:",
 :"childWithClass:code:keyForm:keyData:length:type:",
 :"childWithClass:code:keyForm:keyData:type:",
 :"childWithClass:code:keyForm:keyDesc:",
 :classForScriptingClass, :classNamesForCodes, :classesForScriptingNames,
 :codesForPropertyNames, :context, :convert, :currentEQPreset,
 :currentEncoder, :currentPlaylist, :currentStreamTitle, :currentStreamURL,
 :currentTrack, :currentVisual, :delegate, :descriptionForSpecifier,
 :eject, :elementArrayWithCode, :"elementWithCode:ID:",
 :"elementWithCode:atIndex:", :"elementWithCode:named:", :encodeWithCoder,
 :encoders, :fastForward, :fixedIndexing, :frontmost, :fullScreen, :get,
 :"initWithApplication:specifier:", :initWithBundleIdentifier,
 :"initWithClass:properties:data:", :initWithCoder, :initWithContext,
 :"initWithContext:specifier:", :initWithData,
 :"initWithElementCode:properties:data:", :initWithProcessIdentifier,
 :initWithProperties, :initWithURL, :isRangeSpecifier, :isRunning,
 :lastError, :launchFlags, :mute, :name, :nextTrack, :openLocation, :pause,
 :"play:once:", :playerPosition, :playerState, :playlistWindows,
 :playpause, :positionAfter, :positionBefore, :previousTrack,
 :"print:printDialog:withProperties:kind:theme:", :properties,
 :"propertyWithClass:code:", :propertyWithCode, :qualifiedSpecifier,
 :qualify, :quit, :resume, :rewind, :run, :selection,
 :"sendEvent:id:format:", :"sendEvent:id:parameters:", :sendMode,
 :setCurrentEQPreset, :setCurrentEncoder, :setCurrentVisual, :setDelegate,
 :setEQEnabled, :setFixedIndexing, :setFrontmost, :setFullScreen,
 :setLastError, :setLaunchFlags, :setMute, :setPlayerPosition,
 :setSendMode, :setSoundVolume, :setTimeout, :setTo, :setVisualSize,
 :setVisualsEnabled, :shouldCreateClasses, :soundVolume, :sources,
 :specifier, :specifierDescription, :stop, :subscribe, :timeout, :update,
 :updateAllPodcasts, :updatePodcast, :version, :visualSize, :visuals,
 :visualsEnabled, :windows]
 >> itunes.methods(true, true).grep(/play/)
 => [:playpause, :"play:once:", :playerState, :playerPosition, :playlistWindows]

Method Missing

Method missing is a generic Ruby approach to catching undefined method calls and executing some code based on the undefined method name.

This is a very handy solution to wrap cumbersome Cocoa APIs. It allows you to pick out Cocoa classes you use often and create a more Ruby-like API for them. Let’s take, for example, the AddressBook framework. (see http://developer.apple.com/mac/library/documentation/UserExperience/Conceptual/AddressBook/AddressBook.html). This is an old framework that might not seem very natural at first. Here is a simple example:

framework 'AddressBook'
first_contact =  ABAddressBook.sharedAddressBook.people.first
puts first_contact.valueForProperty(KABOrganizationProperty)
=> "Apple Inc."

The code above is pretty straightforward: it loads the AddressBook framework and uses the ABAddressBook class method called sharedAddressBook to fetch an address book. It then calls the people method on the book, taking only the first person from the array that is returned (#first in Ruby is equivalent to [0]). Of course, before calling this code, I had to dig around in the documentation.

The odd API part is how you access a contact’s property. The Framework expects you to call #valueForProperty on your contact and to pass it a constant that defines the property type. By introspecting first_contact, we find that it’s an instance of the ABPerson class.

The ABPerson C Reference documentation lists all the properties available and the associated constants:

CFStringRef kABFirstNameProperty;
CFStringRef kABLastNameProperty;
CFStringRef kABFirstNamePhoneticProperty;
CFStringRef kABLastNamePhoneticProperty;
CFStringRef kABBirthdayProperty;
CFStringRef kABOrganizationProperty;
CFStringRef kABJobTitleProperty;
CFStringRef kABHomePageProperty;
CFStringRef kABURLsProperty;
CFStringRef kABCalendarURIsProperty;
CFStringRef kABEmailProperty;
CFStringRef kABAddressProperty;
CFStringRef kABPhoneProperty;
CFStringRef kABAIMInstantProperty;
CFStringRef kABJabberInstantProperty;
CFStringRef kABMSNInstantProperty;
CFStringRef kABYahooInstantProperty;
CFStringRef kABICQInstantProperty;
CFStringRef kABNoteProperty;
CFStringRef kABMiddleNameProperty;
CFStringRef kABMiddleNamePhoneticProperty;
CFStringRef kABTitleProperty;
CFStringRef kABSuffixProperty;
CFStringRef kABNicknameProperty;
CFStringRef kABMaidenNameProperty;
CFStringRef kABOtherDatesProperty;
CFStringRef kABRelatedNamesProperty;
CFStringRef kABDepartmentProperty;
CFStringRef kABPersonFlags;

Note

In Objective-C, a constant can start with a lowercase character, but in MacRuby, constants always have to start with an uppercase character. So, for instance, if you want to use the kABFirstNameProperty constant, you have to use the KABFirstNameProperty constant in MacRuby.

Method missing allows us to define a different, potentially nicer API without having to define a method for each constant. Here is what the API will look like:

first_contact.organization # Acme Inc.
first_contact.first_name # Giana
first_contact.last_name # Aimonetti

And here is the implementation:

class ABPerson

  def method_missing(m, *args, &block)
    segments = m.to_s.split('_')
    missing_meth = segments.map{|part| part.capitalize}.join
    constant_name = "KAB#{missing_meth}Property"
    if Object.const_defined?(constant_name)
      self.valueForProperty Object.const_get(constant_name)
    else
      super
    end
  end

end

This might seem quite cryptic if you are new to Ruby and metaprogramming. Basically, we are reopening the ABPerson class and defining a method_missing method to catch our new calls. method_missing takes three arguments: the name of the method (m) as a symbol, an array of arguments (*args), and a block. In our case, our implementation converts the method passed (FirstNameProperty, for instance) to the format expected for a constant (KABFirstNameProperty). If the constant exists, the method dispatches the call using #valueForProperty and passing the constant. Otherwise, we let MacRuby raise an exception.

In this section, we will examine how to implement method missing and how it works so you can also wrap some unneeded complexity in a nice and clean API.

The following method_missing method is defined for Cocoa’s NSSpeechSynthesizer API:

framework 'AppKit'
class Greeter
  attr_reader :user

  def initialize(commands = {})
    voice_type = "com.apple.speech.synthesis.voice.Vicki"
    @voice     = NSSpeechSynthesizer.alloc.initWithVoice(voice_type)
    @user      = NSUserName()
    @commands  = {:hello => "Hello there #{user}, how are you today?",
                  :bye   => "Adios #{user}, come back soon!"}.merge(commands)
  end

  def method_missing(meth_symbol, *args, &block)
    command = @commands[meth_symbol]
    if command
      message = command.respond_to?(:call) ? command.call(self) : command
      @voice.startSpeakingString(message)
    end
  end
end

Open a new macirb session and copy/paste the previous code, or save it in a file and require it from macirb. You can then run a speech synthesizer session as follows:

$ macirb --simple-prompt
>> require 'method_missing_example'
=> true
>> vicki = Greeter.new
>> vicki.respond_to?(:hello)
=> false
>> vicki.hello
=> true
# greetings message is played

As you can see, we were able to call a method that doesn’t exist (hello), thanks to method_missing.

For those not used to reading Ruby code, attr_reader :user is the equivalent of a getter for the instance variable @user, making it available via the user instance method. Our initializer takes an optional argument called commands, which defaults to an empty Hash if not passed. The commands local variable is then merged with a list of default commands. This way, we can pass additional commands when creating our Greeter instance.

Finally, in the method_missing method body, we are checking whether the command object responds to the call method. If it does, we send the call method to it and save the result in the local variable message. Otherwise, we just use the command value (we do that so a command can be defined as an anonymous method). The message is then read by our @voice object.

Let’s see whether passing a new command when creating a new greeter does what we expect:

>> vicki2 = Greeter.new(:howdy => "Howdy cowboy?")
>> vicki2.howdy
# sound of the new greeting defined on the fly.

Now let’s do something a bit more tricky and pass an anonymous method that needs to be executed every single time it’s called:

>> time_cmd = Proc.new{"It's #{Time.new.hour}, #{Time.new.min} and 
>> #{Time.now.sec} seconds"}
>> vicki3 = Greeter.new(:time => time_cmd)
>> vicki3.time
# check that the seconds changed
>> vicki3.time
# time changed

Finally, let’s look at a way of optimizing the performance of our code by using some more metaprogramming. This time, we are going to define methods dynamically at runtime. Instead of always looking up the command and dynamically telling the voice what to say, once a missing method is called, we can add a new method into the class so that we never call the same missing method twice:

framework 'AppKit'
class Greeter
  attr_reader :user

  def initialize(commands = {})
    voice_type = "com.apple.speech.synthesis.voice.Vicki"
    @voice     = NSSpeechSynthesizer.alloc.initWithVoice(voice_type)
    @user      = NSUserName()
    @commands  = {:hello => "Hello there #{user}, how are you today?",
                  :bye   => "Adios #{user}, come back soon!"}.merge(commands)
  end

  def method_missing(meth_symbol, *args, &block)
    command = @commands[meth_symbol]
    if command
      self.class.send(:define_method, meth_symbol.to_s) do
        message = command.respond_to?(:call) ? command.call(self) : command
        @voice.startSpeakingString(message)
      end
      # call the newly defined method
      send(meth_symbol)
    end
  end
end
$ macirb --simple-prompt
>> require 'method_missing_example'
>> vicki = Greeter.new
>> vicki.respond_to?(:bye)
=> false
>> vicki.bye
>> vicki.respond_to?(:bye)
=> true

To optimize this code, we use some Ruby tricks you might not be aware of. The “magic” happens inside the method_missing method implementation. We already talked about method_missing, but we haven’t talked about send and define_method. To illustrate the power of these methods, I’ll explain quickly what the code example does:

def method_missing(meth_symbol, *args, &block)
  command = @commands[meth_symbol]
  if command
    self.class.send(:define_method, meth_symbol.to_s) do
      message = command.respond_to?(:call) ? command.call(self) : command
      @voice.startSpeakingString(message)
    end
    # call the newly defined method
    send(meth_symbol)
  end
end

The first thing we do is check that the missing method matches a defined command. If it does, we define the method on the fly. For that, we define the missing method on the class itself. We can’t call the define_method directly on the class, because define_method is a private method and you can’t call a private method from outside of the object. So, instead, we use the send method. The send method is a way to dynamically dispatch a method by using its name as a string or a symbol. Using send, we can call define_method on the class and pass the missing method name as an argument. The block passed to define_method will become the new method’s body. Once the method is defined, we call it using send again and pass it the method name as a symbol. There is a lot going on in just a few lines of code—take some time to experiment on your own.

As you can see, after calling the bye method a first time, the method becomes defined and the Greeter instance knows that it can respond to it.

Method missing is a very powerful tool and, like most metaprogramming tricks, needs to be used with care. Read more on Ruby metaprogramming to see all the interesting possibilities that are available to you.

Pointers

In the block example earlier in this chapter, we discussed that when using Objective-C or C APIs we might have to deal with pointers. MacRuby exposes a Pointer object that allows you to allocate memory of a given type to a given number of elements. To create a pointer to a float value, for instance, you can choose one of the two constructor options:

pointer = Pointer.new(:float)
# or
pointer = Pointer.new_with_type(:f)

Table 8.1, “Available data types” shows available data types. The list can also be found on Apple’s website (see http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html).

Table 8.1. Available data types

Pointer codePointer typeConstructor example

c

char

Pointer.new(:char)

*

char string

Pointer.new(:string)

i

int

Pointer.new(:integer)

s

short

Pointer.new(:short)

l

long

Pointer.new(:long)

C

unsigned char

Pointer.new(:uchar)

I

unsigned integer

Pointer.new(:uint)

S

unsigned short

Pointer.new(:ushort)

L

unsigned long

Pointer.new(:ulong)

f

float

Pointer.new(:float)

@

object

Pointer.new(:object) or Pointer.new(:id)

B

Boolean

Pointer.new(:boolean)

#

class

Pointer.new(:class)

:

selector

Pointer.new(:selector)


Note

You can create pointers by passing the symbol name as shown above, and also by passing the pointer type code as a string.

You can dereference the pointer the same way you access an array in Ruby.

You can also have a pointer to multiple values. Here is how you create a pointer with bounds:

pointer = Pointer.new(:float, 4)
pointer[0] = 1.0
pointer[1] = 2.0
pointer[2] = 3.0
pointer[3] = 4.0
pointer[0] # => 1.0
pointer[1] # => 2.0
pointer[2] # => 3.0
pointer[3] # => 4.0

To assign a value to the pointer, you can use one of these two methods:

pointer.assign(3.2)
# or
pointer[0]= 3.2

When you use [] and []=, you must be careful not to try to access or set a value out of bounds. Unfortunately, there is no way to find out a pointer’s bounds.

Dereferencing pointers is pretty straightforward and was shown earlier:

pointer = Pointer.new(:integer)
pointer.assign(42)
pointer[0] # => 42

You can also offset pointers using the + and the - methods:

pointer = Pointer.new(:long, 10)
10.times{|i| pointer[i] = i }
pointer2 = pointer + 2
pointer2[0] # => 2
pointer2[1] # => 3
pointer2[2] # => 4

One situation that requires offsetting is when you are using a read method that accepts a pointer of bytes and returns the number of bytes actually read. You can set a pointer, pass it to the method, offset it, and pass it again. Here is a dummy example of such an API:

ptr = dataOut;
fakeAPI(dataIn, dataInLength, ptr, dataOutAvailable
        &dataOutMoved);
ptr += dataOutMoved;
dataOutAvailable -= dataOutMoved;
otherAPI(ptr, dataOutAvailable, &dataOutMoved);

Two types of Objective-C pointers are not directly supported by MacRuby: void pointers and unknown type pointers (also called unsigned pointers). Let’s look at how to work with these types.

Void Pointers

Most methods in APIs based on CoreFoundation use pointers to CFTypeRefs, which technically are pointers to void (also known as void *). If a method expects a void pointer argument, you can pass a pointer of any type.

Let’s look at an example from the Accessibility API:

extern AXError AXUIElementCopyAttributeValue (
       AXUIElementRef element,
       CFStringRef attribute,
       CFTypeRef *value);

As you can see, this is a C function that takes a pointer as its last argument. The reason for using a pointer is that it will hold the function result after it’s called and the caller can retrieve the result from the pointer. The notable issue with this function is that, as mentioned, it returns the retrieved data in the final parameter (value), and therefore requires a pointer as that parameter. To use the method in MacRuby, create a variable (here named titlePtr) and assign it to an object pointer. Pass the pointer value to the function and read the retrieved value just by dereferencing it, as shown here:

framework 'ApplicationServices'
pid = 433
safari = AXUIElementCreateApplication(pid)
titlePtr = Pointer.new(:id)
err = AXUIElementCopyAttributeValue(safari, "AXTitle", titlePtr)
p titlePtr[0] # => "Safari"

Unsigned Pointer

Some methods take unsigned pointers, that is, pointers that don’t have a defined type. Take the FSevents API (see http://developer.apple.com/library/mac/#documentation/Darwin/Conceptual/FSEvents_ProgGuide/UsingtheFSEventsFramework/UsingtheFSEventsFramework.html), for instance.

Here is a MacRuby script that implements this method to watch a specific folder:

callback = Proc.new do |stream, client_callback_info, number_of_events,
                        paths_pointer, event_flags, event_ids|
  puts paths_pointer[0] # => 0
end

paths = [File.expand_path('~/tmp')]
stream = FSEventStreamCreate(KCFAllocatorDefault, callback, nil, paths,
                             KFSEventStreamEventIdSinceNow, 0.0, 0)
FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), KCFRunLoopDefaultMode)
FSEventStreamStart(stream)

NSRunLoop.currentRunLoop.runUntilDate(NSDate.distantFuture)

If you run this code and modify or create a file in ~/tmp, the callback will be triggered, but the output will be 0. You can see why by looking at the callback function’s signature (see http://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/FSEvents_h/index.html//apple_ref/c/tdef/FSEventStreamCallback):

void mycallback(
    ConstFSEventStreamRef streamRef,
    void *clientCallBackInfo,
    size_t numEvents,
    void *eventPaths,
    const FSEventStreamEventFlags eventFlags[],
    const FSEventStreamEventId eventIds[])
{ }

eventPaths is a void pointer and therefore can’t be properly cast by MacRuby. In a case like this, you need to tell MacRuby how to cast the pointer using the #cast! method:

callback = Proc.new do |stream, client_callback_info, number_of_events,
                        paths_pointer, event_flags, event_ids|
  paths_pointer.cast!("*") # cast as a string pointer
  puts paths_pointer[0] # => /Users/mattetti/tmp/
end

paths = [File.expand_path('~/tmp')]
stream = FSEventStreamCreate(KCFAllocatorDefault, callback, nil, paths,
                             KFSEventStreamEventIdSinceNow, 0.0, 0)
FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), KCFRunLoopDefaultMode)
FSEventStreamStart(stream)

NSRunLoop.currentRunLoop.runUntilDate(NSDate.distantFuture)

Some methods optionally take integers in void * parameters. An example is OpenGL’s glVertexPointer function, which, when overloaded, takes an integer passed as the last void * pointer. To do that in MacRuby, you need to create a special pointer type using the Pointer#magic_cookie method, and pass it the integer to use.

This is how you call the function in C:

glVertexPointer(3, GL_FLOAT, 3, (void *) 12)

Using MacRuby you call it this way:

glVertexPointer(3, GL_FLOAT, 3, Pointer.magic_cookie(13))

Finally, the last edge case with void pointers is when an API returns a direct reference to an object. You can’t therefore cast the MacRuby pointer instance value, but should cast the pointer instance itself. Do this via the Pointer’s instance method called to_object.

Here is an example using the C-based Text Input Source Services from the HIToolbox framework. Use the TISGetInputSourceProperty function to get the value of a specified input source (see http://developer.apple.com/library/mac/#documentation/TextFonts/Reference/TextInputSourcesReference/Reference/reference.html).

Here is the function signature:

void* TISGetInputSourceProperty (
   TISInputSourceRef inputSource,
   CFStringRef propertyKey
);

This is how to use it to get the localized name of the current keyboard:

framework 'Cocoa'

keyboard = TISCopyCurrentKeyboardInputSource()
keyboard_name = TISGetInputSourceProperty(keyboard, KTISPropertyLocalizedName)

The problem is that the void pointer returned by the function is a direct reference to an object and, therefore, casting it won't work. In this case, because the documentation is clear about the fact that the void pointer is a direct-typed reference, we need to use #to_object on the pointer instead of casting and dereferencing it:

framework 'Cocoa'

keyboard = TISCopyCurrentKeyboardInputSource()
keyboard_name = TISGetInputSourceProperty(keyboard, KTISPropertyLocalizedName)
keyboard_name.to_object #=> "U.S."

Compilation

A book about MacRuby cannot be complete without mentioning compilation. MacRuby offers two types of compilation: JIT and AOT compilation.

Just In Time compilation (JIT)

This is the default mode. At runtime, prior to being executed, the source code is optimized and compiled into machine code that can run natively on the machine. Even though this process includes a slight delay at startup time, compiled code usually runs faster than the interpreted code, because MacRuby doesn’t have to reevaluate each line of source code each time it is met. This process is totally transparent, since the compilation is done on the fly when a user launches a MacRuby application.

Ahead Of Time compilation (AOT)

Before execution, source code can be compiled directly into machine code (see the Wikipedia entry for Mach-O file at http://en.wikipedia.org/wiki/Mach-O). AOT compilation has two major advantages: it improves startup time and it obfuscates the application source code.

Here is the simplest AOT example—save the following line of code in a file called hello_world.rb:

p ARGV.join(' ').upcase

Or, using the command line, create the file and its content:

$ echo "p ARGV.join(' ').upcase" > hello_world.rb

The code takes all the passed arguments, joins them together separated by a space, and makes the result string uppercase.

Our “program” source code is now ready, so let’s compile it using the MacRuby compiler command line tool, called macrubyc:

$ macrubyc hello_world.rb -o macruby_says

The -o option defines the output file. We can now execute our newly compiled program:

$ ./macruby_says hello world
# => "HELLO WORLD"

Looking at the macrubyc help, we can see that many other options are available:

$ macrubyc --help
Usage: macrubyc [options] file...
    -c                               Compile and assemble, but do not link
    -o <file>                        Place the output into <file>
        --static                     Create a standalone static executable
        --framework <name>           Link standalone static executable with given 
                                     framework
        --sdk <path>                 Use SDK when compiling standalone static 
                                     executable
        --dylib                      Create a dynamic library
        --compatibility_version <VERSION>
                                     Compatibility Version for linking
        --current_version <VERSION>  Current Version for linking
        --install_name <NAME>        Install Name for linking
    -C                               Compile, assemble, and link a loadable object 
                                     file
    -a, --arch <ARCH>                Compile for specified CPU architecture
    -v, --version                    Display the version
    -V, --verbose                    Print every command line executed
    -h, --help                       Display this information

If you look at the contents of a compiled MacRuby application package, you might notice some .rbo files. These files are compiled and loadable object files generated using the equivalent of the following command:

$ macrubyc -C -o file.rbo file.rb

MacRuby can then load the file directly without having to reevaluate it.

Compilation Within Xcode

In most cases, you will use Xcode to develop your application, and the Xcode templates come with a target that does all the work for you. It turns out that there isn’t much to do to compile an app. If you open an existing MacRuby project in Xcode and look at the project’s target information, you will see that the only thing the target does is to call macruby_deploy as such:

$ /usr/local/bin/macruby_deploy --compile --embed

Because no arguments are passed to define the application-bundle path, macruby_deploy relies on two environment variables set by Xcode to define the location of the bundle: TARGET_BUILD_DIR and PROJECT_NAME.

When called with the --compile option, macruby_deploy compiles all the Ruby files available in the resources folder. Xcode copies the source files to this folder automatically for you when you build your project, before calling the external script. These files are compiled as .rbo files that are loaded by the rb_main.rb file.

When the --embed option is set, the MacRuby framework is copied to the application bundle, freeing the application from the need to have MacRuby installed on the user’s system. As part of this process, the linkage is also modified so the compiled code doesn’t break when it’s run on a different machine.

Another useful option that you might need to use when compiling an application from Xcode using macruby_deploy is --no-stdlib. It allows you to make your application smaller by not embedding Ruby’s standard library when embedding MacRuby. If you don’t rely on any libraries from the standard library, it’s recommended that you use this option. If, however, you do rely on a library from the Ruby standard library, you can tell macruby_deploy to embed only specific libraries using the --stdlib option. It takes the names of one or more libraries. You can also embed Ruby gems libraries, as explained in Chapter 13, Using Ruby Third-Party Libraries.

Finally, if you generate some BridgeSupport files or want to force the use of your versions, you can use the --bs option to embed them.

The way compilation currently works in Xcode is that you need to run the second target after building your project. In other words, you will usually develop without compiling. When you are ready, change the target to use the provided Deployment scheme, which calls macruby_deploy, which itself might call the MacRuby compiler if needed. You can see the available macruby_deploy flags by calling its help in the command line, as follows:

$ macruby_deploy --help
Usage: macruby_deploy [options] application-bundle
        --compile                    Compile the bundle source code
        --embed                      Embed MacRuby inside the bundle
        --no-stdlib                  Do not embed the standard library
        --stdlib [LIB]               Embed only LIB from the standard library
        --gem [GEM]                  Embed GEM and its dependencies
        --bs                         Embed the system BridgeSupport files
        --verbose                    Log all commands to standard out
    -v, --version                    Display the version

After you run the Deployment scheme, you can start your application manually or go back to your original scheme and build your app again. If the deployment doesn’t work as expected, you can look in Xcode’s Log navigator to see exactly what happened. You can also set the --verbose flag in the target information to see even more information about what macruby_deploy does. You can also run macruby_deploy manually in the command line, passing it the path to the application bundle file.

Finally, if things don’t seem to work properly and you can’t figure out what is going on, don’t hesitate to clean (Shift + Command + K) the project and build it again.

Site last updated on: November 9, 2011 at 10:00:57 AM PST
Cover for MacRuby: The Definitive Guide