RubyCocoa is sometimes interpreted as a set of bindings to the Cocoa frameworks, which is false. RubyCocoa is a real bridge between the Objective-C and Ruby programming languages.
RubyCocoa will import the Objective-C classes into the Ruby world on demand.
For example, when you access OSX::NSTableView for the very first time in your code, RubyCocoa will retrieve all the necessary information regarding this class from the Objective-C runtime and create a Ruby class of the same name that will act as a proxy. It will also import in the same way all the inherited classes.
As you saw earlier, RubyCocoa creates special proxy objects. Every time you send a Ruby message to a proxy object, RubyCocoa will try to forward it to the embedded Objective-C instance, by translating the message name to an Objective-C selector and asking the Objective-C runtime to forward it.
If an exception is raised from the Objective-C world, RubyCocoa will convert it to a Ruby exception and forward it to you. RubyCocoa uses the Libffi library to call the Objective-C methods implementations. The first method call will go through the #method_missing mechanism, and then the Ruby method will be defined on the Ruby proxy class, allowing all further calls to be direct, and faster.
A typical RubyCocoa program deals with both Objective-C / Cocoa and Ruby objects. RubyCocoa itself makes easy the conversion of objects from one type to the other. There are actually two things that you need to keep in mind:
If you aren't familiar with Objective-C and Cocoa, it is useful to know that Cocoa uses a retain count system to keep track of its objects, implemented in NSObject. This is a very basic mechanism, the user is required to explicitly retain objects that are needed, and release them once they are not needed anymore. There is also the concept of autorelease pools, which allows the user to concatenate some unneeded objects in the same place.
RubyCocoa maintains the Objective-C objects that you will get from the Objective-C world for you, sending retain and release messages when required. You therefore don't have to care about this.
Note that, as a real bridge to the Objective-C runtime, RubyCocoa allows you to actually send retain and release messages. But you may get either leaks or crashes when doing so, so please be careful.
RubyCocoa makes it easy to override an Objective-C method from Ruby, either in a subclass or directly to the class (as you would do in Objective-C using a category).
Once your method is inserted, RubyCocoa will retrieve the signature of the existing Objective-C method and inject a new one to the Objective-C runtime, of the same signature, but which now points to your code.
To accomplish this, RubyCocoa uses the Libffi library to dynamically create a closure that will call the Ruby method, and just passes a pointer to that new closure to the Objective-C runtime.
As you know, in Objective-C there is C. In order to bridge the relevant C parts of an Objective-C framework, such as C structures, functions, enumerations, constants and more, RubyCocoa relies on the BridgeSupport project.
RubyCocoa will interpret at runtime the BridgeSupport files (using the very fast libxml2's xmlTextReader) and accordingly handle their content. It will for instance construct the Ruby proxy classes for the C structures and also create the functions.
Note that the costly operations, such as localizing the symbols, are done on demand, and obviously only once.
RubyCocoa is able to detect APIs that use format strings, like NSLog or NSString.stringWithFormat, and appropriately convert the variable arguments to the types specified in the format string.
RubyCocoa allows you to pass Ruby Proc objects as function pointer arguments. It will then use the Libffi library to dynamically create a closure and pass it to the underlying function/method.
Lazy Class Import
RubyCocoa will import the Objective-C classes into the Ruby world on demand.
For example, when you access OSX::NSTableView for the very first time in your code, RubyCocoa will retrieve all the necessary information regarding this class from the Objective-C runtime and create a Ruby class of the same name that will act as a proxy. It will also import in the same way all the inherited classes.
$ irb -r osx/cocoa
>> OSX::NSTableView.ancestors
=> [OSX::NSTableView, OSX::NSControl, OSX::NSView, OSX::NSResponder, OSX::NSObject,
OSX::OCObjWrapper, OSX::NSKeyValueCodingAttachment, OSX::NSKVCAccessorUtil,
OSX::ObjcID, Object, Kernel]
>> OSX::NSTableView.ancestors
=> [OSX::NSTableView, OSX::NSControl, OSX::NSView, OSX::NSResponder, OSX::NSObject,
OSX::OCObjWrapper, OSX::NSKeyValueCodingAttachment, OSX::NSKVCAccessorUtil,
OSX::ObjcID, Object, Kernel]
Forwarding Messages
As you saw earlier, RubyCocoa creates special proxy objects. Every time you send a Ruby message to a proxy object, RubyCocoa will try to forward it to the embedded Objective-C instance, by translating the message name to an Objective-C selector and asking the Objective-C runtime to forward it.
If an exception is raised from the Objective-C world, RubyCocoa will convert it to a Ruby exception and forward it to you. RubyCocoa uses the Libffi library to call the Objective-C methods implementations. The first method call will go through the #method_missing mechanism, and then the Ruby method will be defined on the Ruby proxy class, allowing all further calls to be direct, and faster.
Object Conversion
A typical RubyCocoa program deals with both Objective-C / Cocoa and Ruby objects. RubyCocoa itself makes easy the conversion of objects from one type to the other. There are actually two things that you need to keep in mind:
- RubyCocoa automatically converts objects that you pass from Ruby to Objective-C. It means that for example you can simply pass a Ruby string to an Objective-C API that expects an NSString. Nothing forbids you to also build from Ruby an NSString and pass it too. If you pass a pure-Ruby object (which doesn't inherit from the NSObject class), RubyCocoa will wrap it in a special Objective-C proxy that will exchange messages with your object. If you want to transform a Ruby primitive object, like a String, to a Cocoa type, you can call the #to_ns method.
- RubyCocoa doesn't automatically convert objects that you get from Objective-C in Ruby, for performance and reliability reasons. However, RubyCocoa provides a nice addition to all Cocoa primitive classes, such as NSString, NSArray, NSDictionary, and more, to make them respond to mostly the same interface as their Ruby equivalents. It means that you can for example call the #each method on an NSArray. If you want to get a pure Ruby object from a Cocoa primitive type, you can call the #to_ruby method.
Cocoa Objects Memory Management
If you aren't familiar with Objective-C and Cocoa, it is useful to know that Cocoa uses a retain count system to keep track of its objects, implemented in NSObject. This is a very basic mechanism, the user is required to explicitly retain objects that are needed, and release them once they are not needed anymore. There is also the concept of autorelease pools, which allows the user to concatenate some unneeded objects in the same place.
RubyCocoa maintains the Objective-C objects that you will get from the Objective-C world for you, sending retain and release messages when required. You therefore don't have to care about this.
Note that, as a real bridge to the Objective-C runtime, RubyCocoa allows you to actually send retain and release messages. But you may get either leaks or crashes when doing so, so please be careful.
Automatic Method Overriding
RubyCocoa makes it easy to override an Objective-C method from Ruby, either in a subclass or directly to the class (as you would do in Objective-C using a category).
Once your method is inserted, RubyCocoa will retrieve the signature of the existing Objective-C method and inject a new one to the Objective-C runtime, of the same signature, but which now points to your code.
To accomplish this, RubyCocoa uses the Libffi library to dynamically create a closure that will call the Ruby method, and just passes a pointer to that new closure to the Objective-C runtime.
Accessing the C Bits
As you know, in Objective-C there is C. In order to bridge the relevant C parts of an Objective-C framework, such as C structures, functions, enumerations, constants and more, RubyCocoa relies on the BridgeSupport project.
RubyCocoa will interpret at runtime the BridgeSupport files (using the very fast libxml2's xmlTextReader) and accordingly handle their content. It will for instance construct the Ruby proxy classes for the C structures and also create the functions.
Note that the costly operations, such as localizing the symbols, are done on demand, and obviously only once.
Format Strings
RubyCocoa is able to detect APIs that use format strings, like NSLog or NSString.stringWithFormat, and appropriately convert the variable arguments to the types specified in the format string.
$ irb -r osx/cocoa
>> OSX::NSLog("hello %f", 42.42)
2007-10-01 18:26:49.591 irb[14340:10b] hello 42.420000
>> OSX::NSLog("hello %f", 42.42)
2007-10-01 18:26:49.591 irb[14340:10b] hello 42.420000
Function Pointers
RubyCocoa allows you to pass Ruby Proc objects as function pointer arguments. It will then use the Libffi library to dynamically create a closure and pass it to the underlying function/method.
$ irb -r osx/cocoa
>> ary = [5, 1, 4, 3, 2].to_ns
=> #<NSCFArray [#<NSCFNumber 5>, #<NSCFNumber 1>, #<NSCFNumber 4>, #<NSCFNumber 3>, #<NSCFNumber 2>]>
>> p = proc { |x, y, context| y.intValue <=> x.intValue }
=> #<Proc:0x0004ac68@(irb):7>
>> ary.sortedArrayUsingFunction_context(p, nil)
=> #<NSCFArray [#<NSCFNumber 5>, #<NSCFNumber 4>, #<NSCFNumber 3>, #<NSCFNumber 2>, #<NSCFNumber 1>]>
>> ary = [5, 1, 4, 3, 2].to_ns
=> #<NSCFArray [#<NSCFNumber 5>, #<NSCFNumber 1>, #<NSCFNumber 4>, #<NSCFNumber 3>, #<NSCFNumber 2>]>
>> p = proc { |x, y, context| y.intValue <=> x.intValue }
=> #<Proc:0x0004ac68@(irb):7>
>> ary.sortedArrayUsingFunction_context(p, nil)
=> #<NSCFArray [#<NSCFNumber 5>, #<NSCFNumber 4>, #<NSCFNumber 3>, #<NSCFNumber 2>, #<NSCFNumber 1>]>