RubyCocoa Programming

irb - Interactive Ruby

You may want to use irb to try the script snippets in this document. irb is a command that lets you use a Ruby interpreter interactively from the command line. You can start an interactive session with RubyCocoa using the following command:

% irb -r osx/cocoa

(NOTE) In Mac OS X 10.9, use /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/irb.

Load libraries

require 'osx/cocoa'      # classes defined in Foundation and AppKit.

or

require 'osx/foundation' # classes defined in Foundation
require 'osx/appkit'     # classes defined in AppKit

A first example with sensory appeal

Let's start with a simple example that will delight your senses – this script will play a sound. Try this with [irb]:

include OSX
files = `ls /System/Library/Sounds/*.aiff`.split
NSSound.alloc.initWithContentsOfFile_byReference (files[0], true).play
NSSound.alloc.initWithContentsOfFile_byReference (files[1], true).play
NSSound.alloc.initWithContentsOfFile_byReference (files[2], true).play

Cocoa classes are Ruby classes

In the following code examples, the comments starting with # => show the string Ruby will output when it executes that line of code.

p OSX::NSObject # => OSX::NSObject
nsstr = OSX::NSObject.description
p nsstr         # => #<OSX::OCObject:0x5194e8 class='NSCFString' id=A97910>
nsobj = OSX::NSObject.alloc.init
p nsobj         # => #<OSX::NSObject:0x51f5b4 class='NSObject' id=976D90>

In RubyCocoa, a Cocoa class is defined as a Ruby class under the OSX module. A Cocoa class is a Ruby class and behaves as a Cocoa object.

Creating a Cocoa object

The normal Cocoa methods are used for creation of Cocoa objects.

obj = OSX::NSObject.alloc.init
str = OSX::NSString.stringWithString "hello"
str = OSX::NSString.alloc.initWithString "world"

Inside RubyCocoa, the created Cocoa object is wrapped in the object of a class called OSX::ObjcID. Usually, you don't need to be conscious of the existence of an OSX::ObjcID class.

Ownership and memory management

The instance of OSX::ObjcID is the real owner of the Cocoa object which self has wrapped. Ownership is automatically lost when the instance of OSX::ObjcID is cleaned by Ruby's garbage collection. Therefore, it is not necessary to worry about memory management issues such as ownership in RubyCocoa.

str = OSX::NSString.stringWithString "hello"
str = OSX::NSString.alloc.initWithString "world"

Although in Objective-C the two lines above differ as to whether ownership is generated or delegated, in RubyCocoa there is no need to be conscious of ownership; the difference between the two techniques shown above is not such an important issue. In principle, it is not necessary to call methods such as release, autorelease, and retain, and you do not need to create NSAutoreleasePools.

Return value of methods

nstr = OSX::NSString.description
p nstr      # => #<OCObject:0x7233e class='NSCFString' id=687610>
p nstr.to_s # => "NSString"

nstr = OSX::NSString.stringWithString "Hello World !"
p nstr      # => #<OCObject:0x71970 class='NSCFString' id=688E90>
p nstr.to_s # => "Hello World !"

nstr = OSX::NSString.stringWithString(`pwd`.chop)
nary = nstr.pathComponents
p nary      # => #<OCObject:0x6bb2e class='NSCFArray' id=3C0150>

ary = nary.to_a
p ary       # => [#<OCObject:0x6a9b8 class='NSCFString' id=3C2B50>,...]

ary.map! {|i| i.to_s }
p ary       # => ["/", "Users", "hisa", "src", "ruby", "osxobjc"]

In RubyCocoa, methods that return Objective-C objects such as NSString and NSArray return Cocoa objects, as you might have guessed from these examples. The return value is not automatically converted to the corresponding Ruby class (String, for example). For NSString and NSArray, to_s and to_a are defined and can be used.

Representing Objective-C message selectors

# play system sounds (2)
sndfiles.each do |path|
  snd = OSX::NSSound.alloc.initWithContentsOfFile(path, :byReference, true)
  snd.play
  sleep 0.25 while snd.isPlaying?
end

This is another version of “playing system sounds”. This shows the other way Objective-C message selectors can be represented in the Ruby world.

In Objective-C:

[obj hogeAt: a0 withParamA: a1 withParamB: a2]

RubyCocoa provides several ways to specify message selectors. The simplest way is to substitute “:” with “_”.

obj.hogeAt_withParamA_withParamB_ (a0, a1, a2)

But because this looks awkward, you can omit the last underscore.

obj.hogeAt_withParamA_withParamB (a0, a1, a2)

When the method name is very long, the relationship between the message selector keyword and each argument is unclear. In order to improve this:

obj.hogeAt (a0, :withParamA, a1, :withParamB, a2)

For Cocoa methods that return the BOOL type (predicate method), use the method name suffix “?” to return a Ruby boolean. If this suffix is omitted, the method will return the value 0 (NO) or 1 (YES). These values behave as true in the Ruby world, so you will get unexpected results.

nary = OSX::NSMutableArray.alloc.init
p nary.containsObject("hoge")   # => 0
p nary.containsObject?("hoge")  # => false
nary.addObject("hoge")
p nary.containsObject("hoge")   # => 1
p nary.containsObject?("hoge")  # => true

Convert Ruby object method arguments when possible

It seems to be usual containsObject of the top and, in case of method to catch Objective-C object as a value of argument, tries conversion even if it just hands Ruby object so long as it is possible.

Handling method name conflicts

klass = OSX::NSObject.class
p klass     # => OSX::OCObject
klass = OSX::NSObject.oc_class
p klass     # => #<OCObject:0x82f22 class='NSObject' id=80819B0C>

When the same method name exists in Ruby and Objective-C, like in the case of Object#class, prefix the method name with “oc_”.

Inheriting from Cocoa

So far, we've discussed existing Cocoa classes and their instances. From this point, we'll discuss the definition and instantiation of derived class of Cocoa, which is also needed when writing RubyCocoa applications. Since the implementation of derived class mechanism for RubyCocoa is a little tricky, there are some restrictions and peculiarities.

Defining a Cocoa-inherited class

The class of the Cocoa objects set up in the GUI definition file (nib file) created by Interface Builder is defined as an inherited class (after 0.2.0). For example, the Controller of the MVC model as described in many Cocoa tutorials is defined in Ruby like this:

class AppController < OSX::NSObject

  ib_outlets :messageField

  def btnClicked(sender)
    @messageField.setStringValue "Merry Xmas !"
  end

end

The inherited class definition of Cocoa in RubyCocoa is similarly described to be the inherited class definition by the usual Ruby in this way.

Defining Interface Builder outlets

The outlet set as the class in the nib file is written to be:

ns_outlets :rateField, :dollerField

in the definition of an inherited class. In fact, ns_outlets is the same as Module#attr_writer. Therefore, a definition can alternatively be given this way:

def rateField= (new_val)
  @rateField = new_val
end

ns_outlets also has an alias called ib_outlets.

Overriding a method

When overriding a method defined by the parent class, it is necessary to declare the override using ns_overrides (alias ib_overrides).

class MyCustomView < OSX::NSView

  ns_overrides :drawRect_, 'mouseUp:'

  def drawRect(frame)
  end

  ...
end

In the argument of ns_overrides what expressed the message selector of Objective-C as the string or the symbol is given. However, the notation for omitting “:” and “_” of the end explained previously cannot be used. It is necessary to describe correctly according to the number of arguments.

To invoke the superclass method in an overriding method, prefix the method name with “super_”.

class MyCustomView < OSX::NSView

  ns_overrides :drawRect_

  def drawRect (frame)
    p frame
    super_drawRect(frame)   # invoke the implementation of NSView#drawRect
  end

end

Instantiating a Cocoa-inherited class

When an instance of a Cocoa-inherited class needs to be created in a Ruby script, it writes like:

AppController.alloc.init  # use this

like the case of the existing Cocoa class. The usual Ruby idiom:

AppController.new    # don't use this

cannot be used (it raises an exception). Although there are various situations in this, since it becomes long, a detailed explanation is omitted here.

These restrictions have deep relation in instance generation being performed in the turn:

Where should initialization code be written?

In Ruby an initialization procedure is written in the “initialize” method generally. But you should be careful in doing so. When the “initialize” method is invoked, a Cocoa object in the Objective-C space is just only given memory. And it is not initialized yet. Therefore, in the “initialize” method, you must not invoke a method implemented in Objective-C space. You should use only a method by Ruby at the point.

If the object needs to be loaded from a nib file, initializing by the “awakeFromNib” method is safest. Doesn't it seem that it is also necessary to actually define the inherited class of Cocoa in most of these cases?

In other cases, initialization is done in the style of Cocoa's init. It is probably a good idea to write to a method with a prefix.

Please do not forget to return self from initialization methods.