3. Bridge Notes

RubyObjC uses a simple convention to call Objective-C methods from Ruby. Colons (:) in selector names are replaced with underscores (_), and all parts of the selector are concatenated to form the Ruby method name.

For example, to call the makeObjectsPerformSelector:withObject: method of NSArray, you would write Ruby code that looked like this:
myArray.makeObjectsPerformSelector_withObject_("danceWith:", me)

Here we assume that myArray is an NSArray containing objects that are able to respond to the “danceWith:” selector, and that me is an acceptable argument value to send with the selector.

When they are properly declared, Ruby methods are easy to call from Objective-C. In fact, the Objective-C code probably won’t be any different from code that callse Objective-C methods. To the Objective-C caller, RubyObjC methods look exactly like methods written in Objective-C.

For a Ruby method to be available from Objective-C, it must first be a method of a class in the hierarchy rooted at ObjC::Object. Usually this means that its Ruby class is derived from ObjC::NSObject or the wrapper for a Cocoa class derived from NSObject, such as ObjC::NSWindowController.

Ruby methods can be added to Objective-C classes either as instance methods or as class methods.

Adding Instance Methods to Objective-C classes from Ruby

To add a Ruby method as an instance method, declare it with the imethod declarator. For example, the following adds a simple instance method to an application delegate class:

imethod example [ruby]
class ApplicationDelegate < ObjC::NSObject
   imethod "applicationDidFinishLaunching:", "v@:@" do |sender|
      ...
   end
end

In this case, the applicationDidFinishLaunching: method is not known to the Objective-C runtime, but because it resembles an action, its signature will be assumed to be “v@:@” and could be safely omitted. There’s more on this below.

Adding Class Methods to Objective-C classes from Ruby

To add a Ruby method as a class method, declare it with the cmethod declarator. For example, the following adds an initialize method to an application delegate class:

cmethod example [ruby]
class ApplicationDelegate < ObjC::NSObject
  cmethod "initialize", "v@:" do
      ....
  end
end

In this case, the “initialize” method is known to the Objective-C runtime and its signature could be safely omitted.

In general, when a method signature is not specified in an imethod or cmethod declaration, RubyObjC will look in the Objective-C runtime for a method with the same name as the method being declared. If one exists, its signature is used. If no such method exists, the bridge has two fallbacks:

1. If the method name looks like an action (a single word ending in ’:’ ), then it is given a default signature of v@:@.

2. If the method name looks like an accessor (a single word containing no ’:’), then it is given a default signature of @@:.

To get the signature to use with a method, concatenate the type codes for its return type and arguments. The return type is first, then there are codes for two hidden arguments, the message receiver ’@’ and the selector being sent ’:’. That’s followed by the codes for each argument.

You can refer to Apple’s Developer Connection web site for a description of the individual type codes.

You can also use RubyObjC to check the signature of any method in the Objective-C runtime. For details, see the next section.

Looking deeper

To easily see whether a method is known to the Objective-C runtime, use the ObjC.get_methods function to get a Ruby hash containing all known methods, indexed by the method name. For example
irb(main):001:0> m = ObjC.get_methods
...
irb(main):002:0> m["initialize"]
=> initialize
irb(main):003:0> m["initialize"].signature
=> "v@:" 
Be careful, though, sometimes multiple methods exist with the same name and different signatures. When this is the case, the get_methods function currently returns only one such method, which is the one that would be used to automatically determine the signature of methods added without explicitly-specified signatures. When there’s doubt, it’s best to explicitly specify a signature.


RubyObjC also provides ways for you to get to the methods associated with specific classes. Here’s one example:

irb(main):007:0> ObjC::NSImageView.imethods.select {|m|                                         
irb(main):008:1*  m.name == "imageAlignment"}[0].signature
=> "i@:" 
For more details, see the online documentation for ObjC::Object, ObjC::Class, and ObjC::Method.

When you use Ruby to create an object in the Objective-C runtime environment, it must be created using the Objective-C protocol for allocation and initialization. Instead of MyClass.new, you must use MyClass.alloc.init or something similar to create instances. RubyObjC makes this more clear by removing the new method for classes derived from ObjC::Object.
irb(main):001:0> ObjC::NSObject.new
NoMethodError: undefined method `new' for ObjC::NSObject:Class
        from (irb):2

Did you find an error? Is something missing? Post your comment or suggestion below!

Comments (0) post