Files
RubyMotion/doc/ReferenceManual.textile
2012-01-20 12:00:25 +01:00

710 lines
40 KiB
Plaintext

h1. RubyMotion Reference Manual
h2. Abstract
This is the RubyMotion reference manual. It documents the latest available version of RubyMotion. This manual closely follows changes in RubyMotion, and it should always be up-to-date.
This is an exaustive manual that covers all the technical aspects of RubyMotion that the developer needs to be aware of in order to develop applications with it. It is not meant to be a guide or a tutorial. Tutorials are available from the same documentation center.
Basic knowledge of the Ruby language and programming concepts are required for this guide.
h2. Getting Started
h3. Overview
RubyMotion is a framework that permits the development of iOS applications using the Ruby programming language.
"iOS":http://www.apple.com/ios/ is Apple's mobile operating system, powering a variety of devices such as iPhone, iPod touch and iPad. Developers can write applications for iOS and submit them to the App Store, Apple's application distribution system.
Conceptually, RubyMotion is a combination of three modules:
* *Runtime*: a custom implementation of the Ruby language for iOS, tightly integrated with the native iOS runtime and customized for embedded devices constrains.
* *Tool*: a command-line toolchain to create and manage RubyMotion projects.
* *Library*: a high-level Ruby library sitting on top of the iOS SDK that facilitates the development of applications.
h3. Installation
An evaluation version of RubyMotion can be downloaded from "http://rubymotion.com":http://rubymotion.com. This version can create and build projects, and run them through the iOS simulator.
A license must be purchased in order to build projects for iOS hardware. This is needed for applications to be tested on device and submitted to the App Store.
RubyMotion installs itself into +/Library/Motion+. A symbolic link to the command-line interface is created as +/usr/bin/motion+.
h3. Software Updates
Software updates can be applied via the command-line.
<pre>
$ sudo motion update
</pre>
This command will grab the latest version of RubyMotion from the network and install it. You must be connected to the Internet to perform that command.
You can always see the version number of the version of RubyMotion installed on the computer.
<pre>
$ motion -v
1.0
</pre>
h2. Runtime
The RubyMotion runtime implements the Ruby language functionality required during the execution of an application. The object model, builtin classes and memory management system are part of the runtime.
Althrough similars in appearance, the RubyMotion runtime has a completely different implementation than "CRuby":http://www.ruby-lang.org/en/, the mainstream implementation. We will cover the main differences in the following sections.
The key feature of the RubyMotion runtime is its tight integration with iOS, which makes applications very efficient.
RubyMotion follows the Ruby 1.9 language specifications.
h3. Object Model
The object model of RubyMotion is based on "Objective-C":http://en.wikipedia.org/wiki/Objective-C, the underlying language runtime of the iOS SDK. Objective-C is an object-oriented flavor of C that has been, like Ruby, heavily influenced by the "Smalltalk":http://en.wikipedia.org/wiki/Smalltalk language.
Sharing a common ancestor, the object models of Ruby and Objective-C are conceptually similar. For instance, both languages have the notion of open classes, single inheritance, and single dynamic message dispatch.
RubyMotion implements the Ruby object model by using the "Objective-C runtime":http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html, the library that powers the Objective-C language, and indirectly, the iOS SDK APIs.
In RubyMotion, Ruby classes, methods and objects are respectively Objective-C classes, methods and objects. And reciprocally, Objective-C classes, methods and objects are available in Ruby as if they were native.
By sharing the same object model infrastructure, Objective-C and RubyMotion APIs can be interchangeable at no additional performance expense.
h4. Objective-C Messages
RubyMotion lets the developer send and define Objective-C messages.
An Objective-C message, also called a selector, can look different than a typical Ruby message, if it contains more than one argument.
Unlike Ruby messages, Objective-C messages contain keywords. Each argument has a keyword associated to it, and the final Objective-C message is the combination of all keywords.
Let's take the following piece of Objective-C code which draws a string.
<pre>
[string drawAtPoint:point withFont:font];
</pre>
In this code, +string+, +point+ and +font+ are variables. The message keywords are +drawAtPoint:+ and +withFont:+. The complete message is the combination of these keywords, +drawAtPoint:withFont:+ , which is sent to the +string+ object, passing the +point+ and +font+ objects as arguments.
Objective-C messages can be sent from RubyMotion using a similar syntax.
<pre>
string.drawAtPoint(point, withFont:font)
</pre>
It is important to keep in mind that the message being sent here is +drawAtPoint:withFont:+. To illustrate more, the same message can be manually dispatched using the +send+ method.
<pre>
string.send(:'drawAtPoint:withFont:', point, font)
</pre>
Objective-C messages can also be defined in RubyMotion. Let's imagine we are building a class that should act as a proxy for our drawing code. The following piece of code defines the +drawAtPoint:withFont:+ message on the +DrawingProxy+ class.
<pre>
class DrawingProxy
def drawAtPoint(point, withFont:font)
@str.drawAtPoint(point, withFont:font)
end
end
</pre>
Note: The syntax used to define Objective-C selectors was added to RubyMotion and is not part of the Ruby standard.
h4. Reading Objective-C Interfaces
TBD
h4. Shortcuts
TBD
h3. Builtin Classes
Some of the builtin classes of RubyMotion are based on the "Foundation framework":http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/ObjC_classic/_index.html, the base layer of iOS.
The Foundation framework is an Objective-C framework, but due to the fact that RubyMotion is based on the Objective-C runtime, the classes that it defines can be naturally re-used in RubyMotion.
Foundation class names typically start with the +NS+ prefix, which arguably stands for NeXTSTEP, the system for which the framework was originally built.
Foundation comes with the root object class, +NSObject+, as well as a set of primitive object classes. In RubyMotion, +Object+ is an alias to +NSObject+, making +NSObject+ the root class of all Ruby classes. Also, some of the builtin classes inherit from Foundation types.
Here is a table showing the ancestors chain of a newly created class, +Hello+, as well as some of the Ruby builtin classes.
|_<. Ruby Class |_<. Ancestors |
| +Hello+ | +NSObject+ → +Kernel+ |
| +String+ | +NSMutableString+ → +NSString+ → +Comparable+ → +NSObject+ → +Kernel+ |
| +Array+ | +NSMutableArray+ → +NSArray+ → +Enumerable+ → +NSObject+ → +Kernel+ |
| +Hash+ | +NSMutableDictionary+ → +NSDictionary+ → +Enumerable+ → +NSObject+ → +Kernel+ |
| +Numeric+ | +Comparable+ → +NSNumber+ → +NSValue+ → +NSObject+ → +Kernel+ |
| +Time+ | +Comparable+ → +NSDate+ → +NSObject+ → +Kernel+ |
A direct consequence of hosting the Ruby builtin classes on Foundation is that instances respond to more messages. For instance, the +NSString+ class defines the +uppercaseString+ method. Since the +String+ class is a subclass of +NSString+, strings created in Ruby also respond to that method.
<pre>
'hello'.uppercaseString # => 'HELLO
</pre>
Respectively, the Ruby interface of these builtin classes is implemented on their Foundation counterparts. As an example, +Array+'s +each+ method is implemented on +NSArray+. This allows primitive types to always respond to the same interface, regardless of where they come from. +each+ will always work on all arrays.
<pre>
def iterate(ary)
ary.each { |x| p x }
end
iterate [42]
iterate NSArray.arrayWithObject(42)
</pre>
But the main purpose of this design is to allow the exchange of primitive types between Objective-C and Ruby at no performance cost, since they don't have to be converted. This is important as most of the types that will be exchanged in a typical application are likely going to be builtin types. A +String+ object created in Ruby can have its memory address passed as the argument of an Objective-C method that expects an +NSString+.
h4. Mutability
The Foundation framework ships a set of classes that have both mutable and immutable variants. Mutable objects can be modified, while immutable objects cannot.
Immutable Foundation types in RubyMotion behave like frozen objects (objects which received the +freeze+ message).
As an example, changing the content of an +NSString+ is prohibited, and an exception will be raised by the system if doing so. However, it is possible to change the content of an +NSMutableString+ instance.
<pre>
NSString.new.strip! # raises RuntimeError: can't modify frozen/immutable string
NSMutableString.new.strip! # works
</pre>
Strings created in RubyMotion inherit from +NSMutableString+, so they can be modifiedby default. The same goes for arrays and hashes.
However, the developer must be careful that it is very common for iOS SDK APIs to return immutable types. In these cases, the +dup+ or +mutableCopy+ messages can be sent to the object in order to get a mutable version of it, that can be modified later on.
h3. Interfacing with C
One does not need to be a C programmer in order to use RubyMotion, however some basic notions, explained in this section, will be required.
Objective-C is a superset of the C language. Objective-C methods can therefore accept and return C types.
Also, while Objective-C is the main programming language used in the iOS SDK, some frameworks are only available in C APIs.
RubyMotion comes with an interface that allows Ruby to deal with the C part of APIs.
h4. Basic Types
C, and indirectly Objective-C, has a set of basic types. It is common in iOS SDK APIs to accept or return these types.
An example are the following methods of +NSFileHandle+, which both respectively accept and return the C integer type, +int+.
<pre>
- (id)initWithFileDescriptor:(int)fd;
- (int)fileDescriptor;
</pre>
Basic C types cannot be created from Ruby directly, but are automatically converted from and to equivalent Ruby types.
The following piece of code uses the two +NSFileHandle+ methods described above. The first one, +initWithFileDescriptor:+, is called by passing a +Fixnum+. The second one, +fileDescriptor+, is called and a +Fixnum+ is returned back to Ruby.
<pre>
handle = NSFileHandle.alloc.initWithFileDescriptor(2)
handle.fileDescriptor # => 2
</pre>
This table describes all basic C types and discusses how they are converted from and to Ruby types.
|_<. C Type |_<. From Ruby to C |_<. From C to Ruby |
| +bool+ |/2. If the object is +false+ or +nil+, +false+, otherwise, +true+. Note: the +0+ fixnum will evaluate to +true+. |/2. Either +true+ or +false+. |
| +BOOL+ |||
| +char+ |/5. If the object is a +Fixnum+ or a +Bignum+, the value is returned. If the object is +true+ or +false+, +1+ or +0+ are respectively returned. If the object responds to the +to_i+ message, it is sent and the result is returned.|/5. Either a +Fixnum+ or a +Bignum+ object.|
| +short+ |||
| +int+ |||
| +long+ |||
| +long_long+ |||
| +float+ |/2. If the object is a +Float+, the value is returned. If the object is +true+ or +false+, +1.0+ or +0.0+ are respectively returned. If the object responds to the +to_f+ message, it is sent and the result is returned.|/2. A +Float+ object.|
| +double+ |||
When using an API that returns the +void+ C type, RubyMotion will return +nil+.
h4. Structures
C structures are mapped to classes in RubyMotion. Structures can be created in Ruby and passed to APIs expecting C structures. Similarly, APIs returning C structures will return an instance of the appropriate structure class.
A structure class has an accessor method for each field of the C structure it wraps.
As an example, the following piece of code creates a +CGPoint+ structure, sets its +x+ and +y+ fields, then passes it to the +drawAtPoint:withFont:+ method.
<pre>
pt = CGPoint.new
pt.x = 100
pt.y = 200
'Hello'.drawAtPoint(pt, withFont: font)
</pre>
It is possible to pass the field values directly to the constructor.
<pre>
pt = CGPoint.new(100, 200)
'Hello'.drawAtPoint(pt, withFont: font)
</pre>
RubyMotion will also accept arrays as a convenience. They must contain the same number and type of objects expected in the structure.
<pre>
'Hello'.drawAtPoint([100, 200], withFont: font)
</pre>
h4. Enumerations and Constants
C enumerations and constants are mapped as constants of the +Object+ class.
For instance, both +NSNotFound+ and +CGRectNull+, respectively an enumeration and a constant defined by Foundation, can be directly accessed.
<pre>
if ary.indexOfObject(obj) == NSNotFound
# ...
end
# ...
view = UIView.alloc.initWithFrame(CGRectNull)
</pre>
Note: some enumerations or constants defined in the iOS SDK may start with a lower-case letter, like +kCLLocationAccuracyBest+ (starting with +k+). Because Ruby constants must always start with a capital letter, their names must be corrected by changing the case of the first letter, becoming +KCLLocationAccuracyBest+ (starting with +K+) in Ruby.
<pre>
locationManager.desiredAccuracy = kCLLocationAccuracyBest # NameError: undefined local variable or method
locationManager.desiredAccuracy = KCLLocationAccuracyBest # works
</pre>
h4. Functions
C functions are available as methods of the +Object+ class. Inline functions, which are implemented in the framework header files, are also supported.
As an example, the +CGMakePoint+ function can be used in Ruby to create a +CGRect+ structure.
<pre>
pt = CGMakePoint(100, 200)
'Hello'.drawAtPoint(pt, withFont: font)
</pre>
Note: most functions in the iOS SDK start by a capital letter. For those who accept no argument, it is important to explicitely use parentheses when calling them, in order to avoid the expression to be evaluated as a constant lookup.
<pre>
NSHomeDirectory # NameError: uninitialized constant NSHomeDirectory
NSHomeDirectory() # works
</pre>
h4. Pointers
Pointers are a very basic data type of C. Conceptually, a pointer is a memory address that can point to an object.
In the iOS SDK, pointers are typically used as arguments to return objects by reference. As an example, the +error+ argument of this +NSData+ method expects a pointer that will be set to an +NSError+ object in case of failure.
<pre>
- (BOOL)writeToFile:(NSString *)path options:(NSDataWritingOptions)mask error:(NSError **)errorPtr
</pre>
RubyMotion introduces the +Pointer+ class in order to create and manipulate pointers. The type of the pointer to create must be provided in the +new+ constructor. A pointer instance responds to +[]+ to dereference its memory address, and +[]=+ to assign it to a new value.
The +NSData+ method above can be used like this in Ruby.
<pre>
# Create a new pointer to the object type.
error_ptr = Pointer.new(:object)
unless data.writeToFile(path, options: mask, error: error_ptr)
# De-reference the pointer.
error = error_ptr[0]
# Now we can use the `error' object.
$stderr.puts "Error when writing data: #{error}"
end
</pre>
In the case above, we need a pointer to an object. +Pointer.new+ can also be used to create pointers to various types, including the basic C types, but also structures.
+Pointer.new+ accepts either a +String+, which should be one of the "Objective-C runtime types":http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html, or a +Symbol+, which should be a shortcut. We recommend the use of shortcuts.
Following is a table summarizing the pointers one can create.
|_<. C Type Pointer |_<. Runtime Type String |_<. Shortcut Symbol |
| +id * + | +'@'+ | +:object+ |
| +char * + | +'c'+ | +:char+ |
| +unsigned char * + | +'C'+ | +:uchar+ |
| +short * + | +'s'+ | +:short+ |
| +unsigned short * + | +'s'+ | +:ushort+ |
| +int * + | +'i'+ | +:int+ |
| +unsigned int * + | +'I'+ | +:uint+ |
| +long * + | +'l'+ | +:long+ |
| +unsigned long * + | +'L'+ | +:ulong+ |
| +long long * + | +'q'+ | +:long_long+ |
| +unsigned long long * + | +'Q'+ | +:ulong_long+ |
| +float * + | +'f'+ | +:float+ |
| +double * + | +'d'+ | +:double+ |
RubyMotion supports the creation of structure pointers, by passing their runtime type accordingly to +Pointer.new+, which can be retrieved by sending the +type+ message to the structure class in question. For instance, the following snippet creates a pointer to a +CGRect+ structure.
<pre>
rect_ptr = Pointer.new(CGRect.type)
</pre>
Pointers to C characters, also called C strings, are automatically converted from and to +String+ objects by RubyMotion.
h4. Function Pointers and Blocks
TBD
h3. Memory Management
RubyMotion provides automatic memory management; the developer does not need to reclaim unused objects.
Since memory can be limited on iOS hardware, the developer must be careful about not creating large object graphs.
RubyMotion implements a form of garbage collection called reference counting. An object has an initial reference count of zero, is incremented when a reference to it is created, and decremented when a reference is destroyed. When the count reaches zero, the object's memory is reclaimed by the collector.
RubyMotion's memory management system is designed to simplify the development process. Unlike traditional Objective-C programming, object references are automatically created and destroyed by the system. It is very similar to Objective-C's ARC, in design, but it is differently implemented.
Object cycles, when two or more objects refer to each other, are currently not handled, but will be in future releases.
h4. Objective-C and CoreFoundation Objects
Objects created by Objective-C or CoreFoundation-style APIs are automatically managed by RubyMotion. There is no need to send the +retain+, +release+ or +autorelease+ messages to them, or use the +CFRetain+ or +CFRelease+ functions.
The following piece of code allocates two +NSDate+ objects using different constructions. In typical Objective-C, the returned objects would need to be differently managed. In RubyMotion, both objects will be entitled to garbage-collection.
<pre>
date1 = NSDate.alloc.init
date2 = NSDate.date
</pre>
In order to prevent an object from being collected, a reference must be created to it, such as setting an instance variable to it. This will make sure the object will be kept alive as long as the instance variable receiver is alive.
<pre>
@date = date1 # date1 will not be collected until the instance variable receiver is collected
</pre>
There are other ways of creating references to an object, like setting a constant, using a class variable, adding the object to a container (such as +Array+ or +Hash+), and more. Objects created in RubyMotion and passed to Objective-C APIs will also be properly referenced if needed.
h4. Collection Times
Object collections are triggered by the system and are not deterministic. RubyMotion is based on the existing autorelease pool infrastructure. iOS creates and destroys autorelease pools for the developer, for instance within the event run loop.
h4. Immediate Types
The +Fixnum+ and +Float+ types in RubyMotion are immediates; they don't use memory resources to be created. The runtime uses a technique called "tagged pointers":http://en.wikipedia.org/wiki/Tagged_pointer to implement this.
The ability to use these types without affecting the memory usage is important as a sizable part of a real-world app is spent in control drawing, which can make intensive use of arithmetic algorithms.
h3. Concurrency
The ability to run code concurrently became critical as multicore processors appeared on iOS devices. RubyMotion has been designed around this purpose.
RubyMotion has the concept of virtual machine objects, which wrap the state of a thread of execution. A piece of code is running through a virtual machine.
Virtual machines don't have locks, and there can be multiple virtual machines running at the same time, concurrently.
Unlike some Ruby implementations, race conditions are possible in RubyMotion, since there is no "Global Interpreter Lock":http://en.wikipedia.org/wiki/Global_Interpreter_Lock (GIL) to prohibit threads from running concurrently. The developer must be careful to secure concurrent access to shared resources.
Different options are available to write concurrent code in RubyMotion.
h4. Threads and Mutexes
The +Thread+ class spawns a new POSIX thread that will run concurrently with other threads. The +Mutex+ class wraps a POSIX mutex, and can be used to isolate concurrent access to a shared resource.
h4. Grand Central Dispatch
RubyMotion wraps the "Grand Central Dispatch":http://developer.apple.com/library/ios/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html (GCD) concurrency library under the +Dispatch+ module. It is possible to execute both synchronously and asynchronously blocks of code under concurrent or serial queues.
Albeit more complicated to comprehend than regular threads, sometimes GCD offers a more elegant way to run code concurrently. GCD maintains for the developer a pool of threads and its APIs are architectured to avoid the need to use mutexes.
h2. Project Management
h3. Creation
RubyMotion projects are created by passing the +create+ command to +/usr/bin/motion+. RubyMotion projects are directories.
<pre>
$ motion create Hello
$ cd Hello
$ ls
Rakefile app resources spec
</pre>
The following table explains the anatomy of a project directory.
|_<. File/Directory |_<. Purpose |
|+Rakefile+ | This file contains the configuration of the project, as well as a default set of tasks. It can be edited to change the configuration or add new tasks. |
| +app/+ | This directory contains the Ruby code of the project. In a new project, a +main.rb+ file is created automatically. |
| +resources/+ | This directory contains the resources files of the project, such as images or sounds. In a new project, this directory is empty. |
| +spec/+ | This directory contains the specification files of the application. In a new project, a default specification file is created automatically. |
RubyMotion projects are managed by the +rake+ tool. Essential tasks will be covered in the following sections. To see the full list of available tasks:
<pre>
$ rake -T
</pre>
h3. Configuration
The +rake config+ task will dump the project configuration. Each configuration variable has a sensible default value that can be manually overriden in the +Rakefile+ file.
|_<. Variable |_<. Discussion |
|+name+ | Project name, as a +String+. The default value is the name passed to +motion create+. |
|+version+ | Project version, as a +String+. The default value is +'1.0'+. |
|+identifier+ | Project identifier, as a +String+, in reverse DNS format. The default value is the concatenation of +"com.yourcompany."+ and the +name+ variable. |
|+delegate_class+ | Name of the application delegate class, as a +String+. The default value is +'AppDelegate'+ and the class is defined in +app/main.rb+.|
|+files+ | Project files, as an +Array+. The default value is the result of the following expression: +Dir.glob('./app/**/*.rb')+ (every +.rb+ file in the +app+ directory). |
|+frameworks+ | The names of iOS frameworks to link against, as an +Array+. It should contain the names of public iOS frameworks, typically present in +/System/Library/Frameworks+. The build system is capable of dealing with dependencies, for instance there is no need to mention +'CoreFoundation'+ if you have +'Foundation'+. The default value is +['UIKit', 'Foundation', 'CoreGraphics']+. |
| +libs+ | Library paths to link against, as an +Array+. It should contain paths to public, system libraries, for example +"/usr/lib/libz.dylib"+. The default value is +[]+, an empty array. |
|+build_dir+ | Path to the directory for build products, as a +String+. It must be relative to the project directory. The directory will be created by the build system if it does not exist yet. If it cannot be created, a temporary directory will be used instead. The default value is +'build'+.|
|+resources_dir+ | Directory for resources files, as a +String+. It must be relative to the project directory. The default value is +'resources'+. |
|+specs_dir+ | Directory for specification files, as a +String+. It must be relative to the project directory. The default value is +'spec'+. |
|+icons+ | List of names of resource files to use for icons, as an +Array+. For example, +["Icon.png", "Icon-72.png", "Icon@2x.png"]+. The files must conform to the "HIG guidelines":http://developer.apple.com/library/ios/#documentation/userexperience/conceptual/mobilehig/IconsImages/IconsImages.html. The default value is +[]+, an empty array. |
|+prerendered_icon+ | Whether the image files in +icons+ are already pre-rendered according to the HIG guidelines. If +false+, iOS will apply a reflective shine effect on the icons. The default value is +false+. |
|+device_family+ | Family of devices to support. Possible values can be: +iphone+, +ipad+ or +[:iphone, :ipad]+ (for a universal application). The default value is +:iphone+.|
|+interface_orientations+ | Supported interface orientations. Value must be an +Array+ of one or more of the following symbols: +:portrait+, +:landscape_left+, :+landscape_right+:, and +:portrait_upside_down+. The default value is +[:portrait, :landscape_left, :landscape_right]+. |
|+platforms_dir+ | Platforms directory where to find SDKs, as a +String+. The default value is determined by the returned value of +'/usr/bin/xcode-select -printPath'+. Giving a new value to this setting must be done first, before changing other settings. |
|+sdk_version+ | Version number of the SDK to use, as a +String+. The default value is the version number of the most recent supported SDK in +platforms_dir+. Example: +'5.0'+. |
|+deployment_target+ | Version number of the SDK to target, as a +String+. The default value is the value of +sdk_version+, but can be lowered to target older versions of iOS. Example: +'4.3'+. |
|+codesign_certificate+ | The name of the certificate to use for codesigning, as a +String+. The default value is the first iPhone Developer certificate found in keychain. Example: +'iPhone Developer: Darth Vader (A3LKZY369Q)'+.|
|+provisioning_profile+ | Path to the provisioning profile to use for deployment, as a +String+. The default value is the first +.mobileprovision+ file found in +~/Library/MobileDevice/Provisioning+. |
|+seed_id+ | The application provisioning identifier, as a +String+. It is a unique (within the App Store) 10 characters identifier generated by the provisioning portal. The default value is the first application identifier found in +provisioning_profile+. |
Custom values for the configuration settings can be added by tweaking the +Motion::App.setup+ block in the +Rakefile+ file.
As an example, let's take the configuration block of a fictional video player application for the iPad. The device family setting has to change from its default value, iPhone, to iPad. Also, the application makes use of an additional framework, AVFoundation, for audio-video functionality.
<pre>
Motion::Project::App.setup do |app|
app.name = 'Awesome Video Player'
app.device_family = :ipad
app.frameworks += ['AVFoundation']
end
</pre>
h4. Files Dependencies
By default, RubyMotion will compile files in the regular sorting order of the filesystem. When a RubyMotion application starts, the main scope of each file will then be executed in that specific order.
Sometimes, the developer will want to customize the order, if for instance one file makes use of a constant defined in another.
<pre>
$ cat app/foo.rb
class Foo
end
$ cat app/bar.rb
class Bar < Foo
end
</pre>
In the example above, using the default order, +bar.rb+ will be compiled before +foo.rb+, resulting in a constant lookup error, because the +Foo+ constant has not been defined yet when we execute the code in +bar.rb+.
To fix that problem, the +files_dependencies+ method can be used in the +Rakefile+. This method accepts a +Hash+ which should be a set of files dependencies.
<pre>
Motion::Project::App.setup do |app|
# ...
app.files_dependencies 'app/bar.rb' => 'app/foo.rb'
end
</pre>
After this change, the build system will compile +foo.rb+ before +bar.rb+.
h4. Using 3rd-Party Libraries
The iOS SDK has a significant amount of functionality built-in that one can use from a RubyMotion project, however, sometimes a developer will need to use a 3rd-party library that provides a missing feature of iOS.
To vendor a 3rd-party library in a RubyMotion project, the source code must be available somewhere on the filesystem. It is recommended to keep the 3rd-party libraries required by the project at the same place, for instance under a +vendor+ directory.
The +vendor_project+ method can be called from the +Rakefile+. Its first argument must be the path to the 3rd-party library, for example +"vendor/OAuth2Client"+. Its second argument must be a symbol representing the project type, like +:xcode+. Other arguments can be provided as a list of key/value objects.
Here is a table summarizing project types and key/value objects. At the time of writing, only Xcode-based projects are supported.
|_<. Project Type |_<. Key |_<. Discussion |
|/6. +:xcode+ | +:xcodeproj+ | Name of the Xcode project file to use. Optional if there is one +.xcodeproj+ file in the directory. |
|| +:target+ | Name of the target to build. If not provided, the default target will be used. Cannot be used at the same time than +:scheme+. |
|| +:scheme+ | Name of the scheme to build. If not provided, the default scheme will be used. Cannot be used at the same time than +:target+. |
|| +:configuration+ | Name of the configuration to build. If not provided, +"Release"+ will be used. |
|| +:headers_dir+ | Path to the directory that contains public headers files, declaring APIs that will be used by the RubyMotion project. The path should be relative to the path provided to +vendor_project+, for example +"Sources/Headers"+. This key is optional. |
|| +:products+ | An +Array+ containing the names of products to use in the RubyMotion project, for example +["libfoo.a"]+. This key can be used when the project builds more than one product and the developer wants to filter what will be used by the app. If not provided, all +.a+ libraries built will be used. |
As an example, assuming our video player project wants to make use of the OAuth2Client 3rd-library library, a +vendor+ directory would be created and the OAuth2Client source code would be added there.
<pre>
$ cd AwesomeVideoPlayer
$ ls vendor
OAuth2Client
</pre>
Then, the +Rakefile+ can be modified as such.
<pre>
Motion::Project::App.setup do |app|
# ...
app.vendor_project('vendor/OAuth2Client', :xcode,
:target => 'OAuth2Client',
:headers_dir => 'Sources/OAuth2Client')
app.frameworks << 'Security' # OAuth2Client depends on Security.framework
end
</pre>
h4. Entitlements
Entitlements confer specific capabilities or security permissions to an application. You may be required by Apple to request an entitlement when trying to access a specific feature of the system.
An application running on an iOS device that does not have the proper entitlement for a functionality will fail at runtime when trying to use such functionality. It will also not be accepted into the App Store.
Entitlements are used during the code-signing part of the build process.
The +entitlements+ method of the +Rakefile+ configuration object returns an empty +Hash+ object by default, that the developer can modify to set appropriate keys and values.
For instance, our video player might require access to the keychain to store the user credentials. According to the documentation, the +keychain-access-groups+ entitlement must be requested, passing a combination of the application provisioning identifier and the application identifier, respectively exposed as +seed_id+ and +identifier+ in RubyMotion.
<pre>
Motion::Project::App.setup do |app|
# ...
app.entitlements['keychain-access-groups'] = [
app.seed_id + '.' + app.identifier
]
end
</pre>
h4. Advanced Info.plist Settings
An iOS app has its configuration described into the +Info.plist+ file, which is located inside the application bundle. This file contains a set of keys and values. It is fully documented in the "Info.plist reference":http://developer.apple.com/library/ios/#documentation/General/Reference/InfoPlistKeyReference/Introduction/Introduction.html guide.
In a RubyMotion project, the +Info.plist+ file is derived from the configuration object exposed in the +Rakefile+ file. For example, the +CFBundleName+ variable in +Info.plist+ is derived from the +name+ variable in the +Rakefile+. Most of the time, the configuration object will let the developer control the necessary settings of his project.
However, it might happen that the developer will want to change an advanced setting of a project. The +Rakefile+ interface does not cover all the possible settings, but it exposes the internal +Info.plist+ data structure that one can modify if needed.
As an example, our video player might need to register a custom URI scheme, so that it can open custom URLs from the web browser, for instance, +x-videoplayer://play+. The +Rakefile+ configuration object does not provide support for this.
The reference suggests that the +CFBundleURLTypes+ key should be used. The key can be manually set by using the +info_plist+ method, which returns a mutable +Hash+ object.
<pre>
Motion::Project::App.setup do |app|
# ...
app.info_plist['CFBundleURLTypes'] = [
{ 'CFBundleURLName' => 'com.mycompany.x-videoplayer',
'CFBundleURLSchemes' => ['x-videoplayer'] }
]
end
</pre>
h3. Build
The +rake build+ task builds the project into the temporary +build+ directory. Two different versions of the project will be built, one to run in the iOS simulator (on the Mac itself) and one to run on the iOS device.
The following steps are performed during the build process:
# It compiles each Ruby source code file into optimized machine code, translating the Ruby syntax tree into an intermediate representation language (using "LLVM":http://llvm.org/), then assembly. The compiler will generate code for either the Intel 32-bit (+i386+) or ARM (+armv6+, +armv7+) instruction sets and ABIs depending on the target.
# It links the machine code with the RubyMotion runtime statically to form an executable. The linker also includes metadata for the C APIs that the project uses, as well as 3rd-party libraries vendored from the configuration.
# It creates an +.app+ bundle and copies the executable there. The +Info.plist+ file is generated based on the project configuration. Each resource file in the +resources+ directory is copied in the bundle. Old resource files, that have been deleted since from the project, will not be present in the application bundle.
# It codesigns the bundle based on the certificate, the provisioning profile and the entitlements specified in the project configuration.
Normally the user does not need to explicitly build the project, as the +build+ task is a dependency of the other tasks.
h4. Multicore Compilation
The compilation of Ruby source files into machine code takes a non-negligeable amount of time.
If the machine used to build the project runs a multicore processor, which is very likely these days, the build system will try to spread the compilation tasks in parallel. This can be very useful when building a project that contains a significant number of files.
The build system uses the value of the +machdep.cpu.thread_count+ sysctl kernel variable to determine know how many compilation tasks can be executed in parallel. For example, a MacBook Pro running an Intel Core i7 processor has 4 available cores, each one being able to run 2 concurrent threads, and the build system will therefore compile 8 files at a time.
It is possible to override the number of concurrent jobs the build system should use by setting the +jobs+ environment variable. It can be set to a lower number if for instance the machine is performing another CPU intensive task that should not be interrupted by the build system.
<pre>
$ rake jobs=1 # Force compilation tasks to be sequential.
</pre>
h4. Cleaning
The +rake clean+ task empties the +build+ directory.
h3. Simulation
The +rake simulator+ task builds the project for the iOS simulator, and runs the application in the simulator.
The default +rake+ task is a shortcut to +rake simulator+.
h4. Universal Applications
A universal application targets both the iPhone and iPad device families (see the +device_family+ project configuration setting for more details).
In this case, it can be convenient to specify which device family the simulator should use. By default, the simulator will use the first device family provided in the project configuration setting.
The +device_family+ environment variable can be set to either +iphone+ or +ipad+ to change this default. For example, forcing a universal application to run on the iPad.
<pre>
$ rake simulator device_family=ipad
</pre>
h4. Cleaning the Sandbox
Each application lives in its own directory inside the iOS simulator sandbox. This directory contains the application bundle, but also the +Documents+ and +Library+ folders, which store the state. When running an application through the simulator, the sandbox will be created if it doesn't exist, otherwise, the application will be copied into the existing sandbox.
Sometimes, the developer may want to clean the application sandbox before running the simulator, in order to start from a fresh state. For instance, if resource files are removed from the project, or if the application has state data that has to be cleaned up.
To perform that, the +clean+ environment variable can be set to any value, which will trigger the removal of the application sandbox before running the simulator.
<pre>
$ rake simulator clean=1
</pre>
h3. Testing
RubyMotion has built-in "Behavior-Driven Development":http://en.wikipedia.org/wiki/Behavior_Driven_Development functionality that the developer can use to cover the specifications of an application. RubyMotion uses a flavor of the "Bacon":https://github.com/alloy/MacBacon framework slightly modified to integrate better with the iOS system.
Specification files are written in the +spec+ directory.
The +rake spec+ task will build a special version of the project that will execute all specification files right after the application launches. A summary will then be printed on the standard output terminal, and the application will exit.
Note: +rake spec+ currently only works with the simulator.
Specification files have full access to the iOS SDK as well as the application classes. Here is a spec that makes sure the application has one active window.
<pre>
$ cat spec/main_spec.rb
describe "My Awesome App" do
it "has a window" do
app = UIApplication.sharedApplication
app.windows.size.should == 1
end
end
</pre>
Assuming the application is properly implemented to follow that specification, +rake spec+ will gracefully exit with a status error of 0.
<pre>
$ rake spec
[...]
My Awesome App
- has a window
1 specifications (1 requirements), 0 failures, 0 errors
</pre>
h3. Deployment
h4. Archiving
The +rake archive+ task builds the project for the iOS platform, and generates in the +build+ directory an +.ipa+ archive suitable for ad-hoc distribution or submissions to the App Store.
The process will fail in case there is no developer certificate or provisioning profile installed, which are required for code-signing.
<pre>
$ rake archive
$ file build/Hello.ipa
build/Hello.ipa: Zip archive data
</pre>
h4. Install on Device
The +rake deploy+ task uploads an archive version of the application to an iOS device.
There must be one iOS device connected via USB to the Mac. The deployment task will attempt to upload the application to the first discovered iOS device on the USB channel.
The process will fail in the following cases:
* No iOS device was found on USB.
* The project builds on a version of iOS greater than the version of iOS running on the device.
* The project doesn't use the appropriate certificate and provisioning profile linked to the device.
* There is a USB connection issue when talking to the device.
Otherwise, the process returns successfully and the application is then available on the device springboard.
h2. Library
TBD.