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 NSAutoreleasePool
s.
- Use Cocoa methods to create Cocoa objects.
- Don't worry about ownership and memory management.
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.
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:
- alloc (in Objective-C world)
- in alloc, create a Ruby object (initialize method is called here)
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.