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
endThere 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
endThis 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}"
endRunning 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
endIf 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)
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
endUse 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
endNote
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 dispatchNote
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!"
endIn 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] # => 845MacRuby’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 secondsAs 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 secondsInstead 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_findTo 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.393145This 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 }
endThis 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
endIf 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 --helpIn 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/BridgeSupportA 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_metadataScriptable 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”).
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.appThe 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 iTunesNote
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.bridgesupportNow 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.playpauseIf 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 playlistThe 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
endThis 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
endOpen 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
endThe 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 code | Pointer type | Constructor example |
|---|---|---|
|
c |
char |
|
|
* |
char string |
|
|
i |
int |
|
|
s |
short |
|
|
l |
long |
|
|
C |
unsigned char |
|
|
I |
unsigned integer |
|
|
S |
unsigned short |
|
|
L |
unsigned long |
|
|
f |
float |
|
|
@ |
object |
|
|
B |
Boolean |
|
|
# |
class |
|
|
: |
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] # => 4One 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(' ').upcaseOr, using the command line, create the file and its content:
$ echo "p ARGV.join(' ').upcase" > hello_world.rbThe 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_saysThe -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 informationIf 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.rbMacRuby 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 --embedBecause 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 versionAfter 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.






Add a comment



Add a comment