TweetFollow Us on Twitter

RubyCocoa-Part 1

Volume Number: 24 (2008)
Issue Number: 04
Column Tag: Programming


A new way to write Cocoa applications-Part 1

by Rich Warren

I admit it. I have a soft spot for Ruby. That probably comes as no surprise, if you've read my earlier articles (particularly Introduction to Ruby on Rails and Ajax on Rails, both available online at Needless to say, I'm quite giddy with Leopard's scripting language support. Leopard has elevated Python and Ruby to. . .um. . .not first class citizens. Not quite. But they make a strong second-class showing.

In fact, my biggest complaint comes from the terminology. Apple's own documentation refers to both Ruby and Python as scripting languages. Scripting Languages? Sure, they are both interpreted languages, but the word "scripting" makes them sound like limited, little things. Trust me, you can use these languages to do a lot more than just write scripts. We have two, full-blown, dynamic, object oriented programming languages, and Leopard puts their power at our fingertips.

New Ruby and Python Features

Ruby and Python are not new to OS X. Tiger shipped with both languages installed (though, if you've read my previous articles, you know that the Tiger version of Ruby kinda sucked). Leopard, however, kicks the support up a notch. They've invested a lot of time into getting the details right. While they may not always succeed, I appreciate the effort.

For example, Xcode comes with templates for a variety of Ruby and Python projects. Syntax highlighting and code completion work as expected. Most importantly, Leopard integrates both languages more tightly into the operating system. Both include a bridge to the Objective-C runtime, and both can communicate with scriptable applications.

The Bridge to Objective-C

Leopard ships with the popular RubyCocoa and PyObjC libraries already installed. Developers can use these libraries to write Cocoa applications in either Ruby or Python, respectively. Both languages have access to Leopard's core technologies, including Core Data, Bindings and Document-based applications. These libraries even support the new rock-star frameworks like Core Animation.

But, why would you want to use Ruby or Python? Some might say they're addictive; once you start using them it's hard to go back (trust me, I use Java for my day job). But, you can find other reasons as well. Both Ruby and Python are very expressive languages. You can get a lot of work done with very little code. This makes them ideal choices for rapid development and prototyping.

Additionally, Objective-C, Ruby and Python share many common concepts and design choices. They are all dynamic, object-oriented languages. Ruby and Objective-C in particular, were both heavily influenced by Smalltalk. This common ground helps us coordinate our code across the different languages.

And we can freely mix our code. We can use Ruby subclasses of Objective-C classes, or Python delegates for Objective-C objects. We can transparently call one language from the other. This gives us more power and more flexibility than any one language would have on its own. We have access to each language's libraries. We can exploit their individual strengths, using one language to spackle over the other's weaknesses.

Unfortunately, Cocoa seems to have a one-bridge-at-a-time rule. Mixing either Ruby or Python with Objective-C works just fine. But mixing Ruby and Python quickly becomes problematic. Both frameworks try to load the BridgeSupport dylib, and this can cause errors. Some developers have posted workarounds on the web, but they tend to feel rather hackish to me. Still, I think this issue will smooth itself out with future updates.

The Bridge to OSA

We can also use Ruby and Python to communicate with scriptable applications using the Open Scripting Architecture (OSA). RubyCocoa and PyObjC already give us full access to the native Scripting Bridge, but I think this often becomes unwieldy. We end up writing Ruby (or Python) versions of Objective-C calls on AppleScript APIs.

Fortunately, each language has its own library to simplify scripting: RubyOSA for Ruby and py-applescript for Python. Unfortunately, Leopard does not include these libraries. You need to install them on your own.

Ruby in Leopard

For the rest of this article will dig into the Ruby-specific additions to Leopard. Python has comparable features, but for simplicities sake, I will focus on what I know. Ruby comes ready for serious development. Leopard's installation includes several important libraries: rake, Mongrel, Ferret, Capistrano, sqlite3-ruby, dnssd (aka Bonjour) and Rails. Of course, given the frantic rate of Ruby development, many of these libraries have already grown long in the tooth. Still, that's not a huge concern. Leopard also includes RubyGems.

RubyGems is a command-line package manager for Ruby. It allows us to quickly and easily install and update Ruby libraries. For example, to update the current version of Rails, just type:

gem update –include-dependencies rails

However, if you're like me, the thought of wildly upgrading your system libraries makes your stomach churn. What happens if something goes wrong? Sooner or later, something always goes wrong. Won't this just screw up my system?

Well, put down that bottle of Pepto. Leopard carefully separates its pre-installed libraries from the user-installed libraries and updates. Accidentally updating to an unstable version doesn't change your original system files. Simply uninstall the offending library, and you're good to go. This also makes rolling back to factory defaults quite easy. Simply delete the user-gems folder.

Leopard keeps built-in libraries in the /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/ folder. The gems subfolder contains the actual libraries, while the doc subfolder contains both ri and html documentation.

When you run gems, it saves new libraries to the /Library/Ruby/Gems/1.8/ folder. Again, you can find the libraries in the gems folder, while documentation is. . .wait for it. . .in doc.

Just to be complete, Leopard stashes the RubyCocoa files in a third location: /System/Library/Frameworks/RubyCocoa.framework

I highly recommend poking around in these directories–particularly the RubyCocoa header files. They can give you a good feel for the breadth of options available.

The Limits of RubyCocoa

Of course, there are no magic bullets, and RubyCocoa has its share of downsides.

Slow, slow, slow

As much as I love Ruby, it is a fairly slow, interpreted language. RubyCocoa code will run significantly slower than equivalent Objective-C code. Depending on the application, this may not be a problem. After all, GUI applications spend most of their time waiting on the user anyway.

Besides, if a RubyCocoa program feels slow, you can always profile it and look for bottlenecks. Once you identify likely problems, you can either redesign your code to eliminate the bottleneck, or convert it into faster, Objective-C code.

Finally, the newly released Ruby 1.9 uses a new, faster virtual machine. Unfortunately, as I write this, Ruby 1.9 only comes as a development release–it's not quite ready for prime time.

Not thread safe

Ruby 1.8 is not thread safe. You cannot call Ruby code on multiple native threads. To prevent possible problems, the bridge actually reroutes all Ruby calls from Objective-C to the application's main thread. However, as we will soon see, you can still use Ruby's threads within your Ruby code, which gives us a partial workaround. Again, the production release of Ruby 1.9 should fix this.

Xcode's debugger does not work

You cannot use Xcode's debugger on your ruby code. However, you can use Ruby's debugging tools along with new Leopard tools like DTrace and Instruments. This isn't an ideal solution, but it works.

RubyCocoa does not support Objective-C garbage collection

To me, this was probably the most disappointing limitation. Ruby itself uses garbage collection, but your Objective-C code must continue to manage its own memory. Somehow this just feels wrong.

Finding Documentation and Getting Help

Apple has included a number of documents and examples to help you get started. You can find the following articles linked off the "Introduction to Ruby and Python Programming Topics for Mac OS X" web page (

Ruby and Python on Mac OS X

Building a RubyCocoa Application: A Tutorial

Using Scripting Bridge in PyObjC and RubyCocoa Code

The Leopard Technology Series for Developers also includes a nice introductory article at h

However, if you want documentation about the frameworks that RubyCocoa supports, prepare for disappointment. You might find a promising folder at /Developer/Documentation/RubyCocoa. Unfortunately, this only contains a few files in Japanese. The actual RubyCocoa documentation is missing. Fortunately, we can fix this. . .more or less.

You need to download the latest RubyCocoa source release from Untar the source files, then run the following commands:

ruby install.rb config
ruby install.rb doc

This will create ri and html documentation for most of the Cocoa libraries supported by RubyCocoa. However, the documentation has two small problems.

First, it does not cover all the libraries that RubyCocoa supports.

Second, and more importantly, the installer tends to break whenever Apple updates their reference libraries. The RubyCocoa team tries to keep up with the latest changes, but they are chasing a moving target. The 0.13.0 release will work fine for a fresh install of Xcode 3.0, but if you've updated your reference libraries, it will fail. In that case, try the latest build from the SVN trunk using the following command:

svn co\
/src rubycocoa

Don't be surprised when you see errors while parsing Apple's documentation. RubyCocoa should still create documentation for most Cocoa classes.

Alternatively, you can simply look up the Cocoa classes directly from Apple's reference library. As we will see, you can easily translate an Objective-C method into a RubyCocoa call.

Even with all the tutorials, introductory articles and reference libraries, RubyCocoa has a number of dark corners. Fortunately, you can find several other resources to help you master RubyCocoa–or at least help you ask intelligent-sounding questions.


Leopard's developer tools include 40 sample projects for RubyCocoa. You can find these in the /Developer/Examples/Ruby/RubyCocoa directory. These samples range from old standbys (yet another Currency Converter) to video games. Take some time to browse these projects. They can give you a real feel for using RubyCocoa effectively.

Web Sites

While a quick search on Google brings up 370,000 matches for "RubyCocoa", I highly recommend two sites: the RubyCocoa project pages at ( and RubyCocoa Resources ( Both provide a range of useful articles. The introductory topics help you get started, while the advanced topics keep you coming back for more.

The Last Resort

The RubyCocoa community has an active mailing list. In my experience, everyone is helpful and kind. But, please: don't waste their time. Try to research the issue on your own. Then, if you're still stuck, check out RubyCocoa Talk.

You can subscribe to RubyCocoa Talk at

Our Project

To really understand something, sometimes you need to just jump in. Therefore, the rest of this article, will focus on building a simple RSS reader using RubyCocoa.

Why another RSS reader? Leopard already comes with built in RSS features for both Safari and Mail, not to mention many third-party applications. Still, I wanted to try something a bit messier than the typical toy project. By tackling a problem with rough edges, we get a better feel for RubyCocoa's strengths and weaknesses.

Additionally, I wanted a project that would demonstrate the following four points:

The project should use a RubyGem library.

The project should use key Cocoa technologies, like Core Data and Bindings.

The project should use RubyOSA to communicate with an existing, scriptable application.

The project should be implemented entirely in Ruby.

Our RSS reader will read and parse RSS feeds using the FeedTools gem. The application will use both Core Data and Bindings extensively. In part 2, we will send enclosures to an iTunes playlist using RubyOSA. And, except for a single Objective-C class, we will only write Ruby code.

3.859 out of 4 isn't bad.

Installing the Gems

First, a quick word of warning. Don't update RubyGems or any of your libraries just yet. As we will see, this may complicate things. Nothing we can't fix, but you might want to avoid problems when you can.

RubyGems is a powerful package manager for Ruby libraries. It is also a complex, command line tool. A full explanation is beyond the scope of this article, but the table below should get you started. For more information than you could ever possibly want, check out the RubyGem manuals at

Note: many of these commands (especially install, update and uninstall) require root access. You typically launch them as sudo commands.

Also, I deliberately left one command off the list: gem update –system. This updates the RubyGem system itself. Unfortunately, unlike the other gem updates, this actually changes your system files, and these changes are not easily undone.

I strongly recommend leaving this command alone. Let Apple manage the RubyGems system. As I'm writing this, they just updated RubyGems as part of the 10.5.2 release, so it should stay reasonably current. Modify the gems as much as you want, but leave the system alone.

Most of the time, you will use simple install and update commands; however, the others can come in handy when things go wrong. Updates do not always proceed as smoothly as I would like. Sometimes they leave a gem or two behaving badly. I often find that uninstalling and reinstalling the offending gem (and possibly its dependencies) sorts things out.

Now that we understand the basics of RubyGems, our first step should be the simplest. We just need to install our project's RubyGem libraries. In theory, this should only require typing the following command, entering your password when prompted.

sudo gem install feedtools

Unfortunately, life is never this easy. The FeedTools library contains the deprecated ruby-gem command. As long as you're still running the version of RubyGems that came with Leopard, you shouldn't have any problems. The library just logs a few warnings to the console. However, newer versions of RubyGems no longer recognize this command. Bottom line, if you've updated to 10.5.2, you have the new version of RubyGems, and the FeedTools library will crash.

To fix this, you simply need to edit feed_tools.rb. You can find this file at /Library/Ruby/Gems/1.8/gems/feedtools-0.2.26/lib/feed_tools.rb. Globally replace "require-gem" with "gem".

Creating the Project

Now, we can create our project. Open Xcode, and from the File menu select New Project.... In the Assistant window, scroll down and select Cocoa-Ruby Core Data Application. Click Next.

Creating our Cocoa-Ruby Core Data Application

Enter RubyRSS for the project name. Set the project directory to whatever you wish. Click Next again. Abracadabra. . .project created!

But, lets take a quick look at what Xcode has done.

MainMenu.nib and RubyRSS_DataModel.xcdatamodel are standard files for any Core Data application. The first defines our user interface. The second defines our data model. We will take a closer look at both in just a second.

Open main.m. This is the starting point for our application. As you can see, a RubyCocoa application's main simply imports the RubyCocoa runtime, then launches rb_main.rb using the RBApplicationMain() function.


#import <Cocoa/Cocoa.h>
#import <RubyCocoa/RBRuntime.h>
int main(int argc, const char *argv[])
   return RBApplicationMain("rb_main.rb", argc, argv);

Our Ruby code really starts with rb_main.rb. The default implementation loads the RubyCocoa library, locates the application's resource path, then loads any files ending with .rb using Ruby's require() method. This creates all our Ruby classes. Once finished, rb_main.rb calls NSApplicationMain(), which initializes and runs the Cocoa application.


require 'osx/cocoa'
def rb_main_init
   path = OSX::NSBundle.mainBundle.resourcePath.fileSystemRepresentation
   rbfiles = Dir.entries(path).select {|x| /\.rb\z/ =~ x}
   rbfiles -= [ File.basename(__FILE__) ]
   rbfiles.each do |path|
      require( File.basename(path) )
if $0 == __FILE__ then
   OSX.NSApplicationMain(0, nil)

With our Ruby classes now defined, we can access them from Objective-C. Unfortunately, we cannot directly import Ruby classes into Objective-C files; however, we can indirectly access the classes by name. While we won't do this in our application, the following code snippet shows the basic technique. It creates a MyRubyClass object defined in a MyRubyClass.rb file. It then calls the object's mySampleMethodCall().

Accessing Ruby from Objective-C

Class myRubyClass = NSClassFromString(@"MyRubyClass");
id ruby = [[myRubyClass alloc] init];
[ruby mySampleMethodCall]

Notice how RubyCocoa seamlessly translates objects between Ruby and Objective-C. Usually, you won't need to worry, things just work.

Sooner or later, however, you will rub up against one of the rougher edges. For example, RubyCocoa converts Ruby objects into Objective-C equivalents when possible. This means you can pass Ruby Strings to Objective-C methods. RubyCocoa will automatically convert them into NSStrings.

However, the reverse is not true. RubyCocoa will place a Ruby wrapper around Objective-C classes, and will sometimes add convenience methods (like adding each() to NSString, NSArray and NSDictionary), but it does not convert the classes.

So, if RubyCocoa calls an Objective-C method that returns a string, the Ruby code will get an NSString, not a Ruby String. A quick call to to_s() fixes this, but it can cause bugs if you're not careful. Also, Ruby and Objective-C sometimes have very different ideas about booleans. We'll take a closer look at that little wrinkle later.

My advice, ignore object conversions until they cause problems. This is best dealt with on a case-by-case basis.

Finally RubyRSSAppDelegate.rb acts as a Ruby-implemented delegate for our application. Feel free to poke around this file. However, you'll find the most interesting bits at the very beginning. This class not only imports the Core Data framework, it also subclasses NSObject. This just demonstrates how easily Ruby and Objective-C code can mix.


This Ruby code imports a Cocoa framework, then subclasses an Objective-C object.

require 'osx/cocoa'

OSX.require_framework 'CoreData'

class AppDelegate < OSX::NSObject


Defining the Model

Building a Core Data model is beyond the scope of this article. For more information, take a look at the Core Data Tutorial video (,/a>) or Apple's article on creating managed object models with Xcode (

For simplicity's sake, lets import our model from the online source code for this article. First, download the source code from Delete RubyRSS.xcdatamodel from your project. Select Also Move to Trash when prompted. Then, select Project... Add to Project... In the file dialog, select RubyRSS.xcdatamodel from the source code's folder. Press Add. In the next dialog, make sure Copy items into designation group's folder (if needed) is selected. Click Add again.

Now, open RubyRSS.xcdatamodel, and let's poke around inside. RubyRSS uses a simple model with only three Entities: Feed, Post and Enclosure.

RubyRSS's Data Model

The Feed entities represent our RSS subscriptions. Feed has three attributes: name, url and count. It also has a too-many relationship with Post.

Post has two attributes: title and text. Post also has two relationships: one points back to Feed, while a to-many relationship points to Enclosure. So far, so good–this isn't exactly rocket science.

Finally, Enclosure has two attributes: url and isAudio. It also has a single relationship with Post.

The attributes have straightforward data types. I've listed the details below, but nothing should come as a surprise. Also, if you look carefully at the model, you will see that I've placed some restrictions on the data. In general, I recommend making your data as restrictive as possible; however, we don't need data validation for this tutorial, so I'll let you explore it on your own.

Enclosure and Post are both NSManagedObjects. However, Feed's count attribute needs a bit of special attention. Count represents the number of posts associated with this feed. To get this behavior, we will need to subclass NSManagedObjects and override the count() accessor.

Now, as I said earlier, I hoped to implement everything using Ruby. This will be the one exception. Trying to write this in Ruby just creates problems; the default AppDelegate implementation automatically creates Key Value Coding (KVC) wrappers for any attributes declared in the NSManagedObjectModel. Since this occurs after our classes have loaded, our custom count() method gets clobbered.

We could fix this, but it's easier to write ManagedFeed in Objective-C, and I'm all about the pragmatic.


This is the header file for our ManagedFeed class.
#import <Cocoa/Cocoa.h>
@interface ManagedFeed : NSManagedObject {


This is the implementation of our ManagedFeed class.

#import "ManagedFeed.h"
@implementation ManagedFeed
+(NSSet*) keyPathsForValuesAffectingCount {
   NSSet *set = [super keyPathsForValuesAffectingValueForKey:@"posts"];
   return [set setByAddingObject:@"posts"];
-(int)count {
   id posts = [self valueForKey:@"posts"];
   NSArray * all = [posts allObjects];
   return [all count];

You can find a detailed description of the keyPathsForValuesAffecting<key> method in the NSKeyValueObserving protocol reference. Essentially, this method describes the dependencies for a given key. KVO uses this to determine if and when the key may have changed. In our code, count could change whenever the value of the post key changes. We could specify this by just returning a set that contains @"post".

However, our implementation is a little more complicated. Apple recommends requesting an initial set of keys from the super class, then appending your own key paths to that set. In this tutorial, the call to the super class will always returns an empty set. However, this implementation protects us from future changes.

In the count method, we return the number of posts associated with this feed. We get a copy of the posts relationship using KVC . Then we extract an NSArray containing these posts. Finally, we return the number of objects in our NSArray.

Building the Controllers

Apple now recommends building your controllers before designing your interface. You can still create controller objects within Interface Builder and then export them back to Xcode. You can even export your controllers in Ruby and Python; however, I could not get the resulting code to run. Best to follow their advice and just write the controllers yourself.

Just like the standard Objective-C versions, our Ruby controllers combine outlets, actions and possibly a few helper functions. Outlets represent the UI elements that we will need to programmatically interact with. Actions represent UI-driven events.

Fortunately, RubyCocoa provides an attr_accessor-like method for defining outlets. For those not familiar with attr_accessor, it takes any number of symbols, and creates an instance variable for each one. Attr_accessor also creates the getter method <symbol>() and the setter method <symbol>=(). For example, attr_accessor :name creates @name, name() and name=().

Similarly, ib_outlet takes a comma-separated list of symbols. It converts each symbol into an instance variable with the same name. A corresponding outlet will also appear in Interface Builder.

Note: you should avoid using attr_accessor in your RubyCocoa code. Unfortunately, attr_accessor does not create KVC compliant variables, so we cannot connect to them using Bindings. The getter works fine, but Cocoa expects a set<Symbol>() setter (setName() in our example).

Fortunately, RubyCocoa provides kvc_accessor. Kvc_accessor works identically to attr_accessor, but creates KVC compliant methods.

RubyCocoa also simplifies declaring KVC dependencies. The kvc_depends_on() method takes two parameters: an array of symbols representing the dependencies, and a single symbol representing the calculated attribute.

Basically, this method replaces Objective-C's keyPathsForValuesAffecting<key>(). Take a look at our ManagedFeed.m file again. The keyPathsForValuesAffectingCount method defines count's dependency upon posts. In Ruby, we could replace that method with a single line:

kvc_depends_on([:posts], :count)

Finally, RubyCocoa elegantly handles actions. Simply define a method with a single parameter, usually named sender. After the method, add a call to ib_action() passing in the method's name as a symbol.

Sample RubyCocoa Action

def myAction
ib_action :myAction

Now, the Rubyists out there have undoubtedly noticed that the RubyCocoa formatting looks a bit odd. Most of this creeps in when we translate Objective-C syntax into Ruby.

Objective-C's syntax uses both named arguments and colons–neither of which translates nicely. Therefore, when referring to an Objective-C method, concatenate all the pieces of its signature, and replace the colons with underscores.

[canvas print: text withFontColor: red];


canvas.print_withFontColor_(text, red)

As a bit of syntactic sugar, RubyCocoa allows you to drop the final underscore. So, print_withFontColor_() becomes print_withFontColor(). Note: the Ruby and Python Programming Topics for Mac OS X article claims that this option is disabled by default. This is not true. In most cases, you can use the two variants interchangeably. The exceptions, however, can cause real pain.

When Objective-C calls a Ruby method that overrides an Objective-C method (Ah, yes. She knows that I know that she knows that I know. . . .), RubyCocoa looks for the method signature without the trailing underscore. So, just to prevent possible problems, I recommend universally dropping the last underscore.

For consistency, I've tried to use camel case for actions (likeThis). Pure-ruby helper functions have the more-traditional underscore names (like_this).

OK, enough babbling. Let's look at the code. We will have two windows in our UI, the main window, and a dialog for adding new feeds Let's create a controller for each: MainController.rb and AddFeedController.rb respectively.


This class acts as the controller for our main window. It responds to all the main window's actions, and makes changes to the data model. It will also coordinate with both the FeedTools and RubyOSA libraries when necessary.

require 'osx/cocoa'
# Controller for the Main window.
class MainController < OSX::NSObject
   ib_outlet :feeds, :posts, :enclosures, :web_view, :posts_table, 
      :enclosures_table, :progress, :app_delegate
   # accessor for the current Feed collection.
   def feeds
      return @feeds.arrangedObjects
   # Add remaining methods here

Here, we're building a subclass of NSObject. We start by declaring a slew of outlets for Interface Builder. The feeds() accessor returns an array containing all our Feed entities. This represents all currently subscribed feeds.

The next two methods override NSObject methods. The Cocoa framework will automatically call these.

NSObject Methods

# Initializes the Main Window after it is loaded from the NIB.
def awakeFromNib
   @posts.addObserver_forKeyPath_options_context(self, "selection", 
      0, nil)
# This listener method will be called whenever the Post Table's selection 
# changes. It updates the HTML in the web view.
def observeValueForKeyPath_ofObject_change_context( key_path, object, 
   change, context)
   set_html if @posts.isEqual(object)

The framework calls our awakeFromNib() method after all objects have been loaded from the nib file, and once all outlets are set. We can use this method to perform any additional initialization. In our case, we make the NSProgressIndicator invisible when not in use. We also force our controller to listen for any changes to the @posts selection.

The framework now calls observeValueForKeyPath_ofObject_change_context() whenever @posts's selection changes. We simply verify that we're receiving an update from @posts, then call the set_html() helper method.

Note: As I mentioned earlier, you must drop the final underscore from this method's name. Otherwise, Key Value Observing (KVO) cannot find our implementation.

Next, we declare two actions: sendToItunesAction() and refreshFeedsAction(). Currently, they just print a message to the console.


# This action sends the currently selected Enclosure to iTunes.

def sendToItunesAction(sender)
   puts "Send to iTunes"
ib_action :sendToItunesAction
# This action refreshes all the feeds.   
def refreshFeedsAction(sender)
      puts "Refresh Feeds"
ib_action :refreshFeedsAction

Add_feed() adds a new Feed entity to the managed object context. It then fills in the feeds attributes. Notice that it leaves the posts relationship blank. We don't have any posts yet.


# Adds a new feed to the Managed Object Context
def add_feed(name, url) 
   moc =  @app_delegate.managedObjectContext
   new_feed = OSX::NSEntityDescription\
   new_feed.setValue_forKey(name, "name")
   new_feed.setValue_forKey(url, "url")

Finally, set_html() gets the text from our currently selected post. We then display this text in our web view.


# Helper Function: Updates the HTML displayed by the Web View to the text 
# of the currently selected post.
def set_html
   index = @posts_table.selectedRow
   frame = @web_view.mainFrame
   # If nothing is selected, just return.
   if index < 0 then
      frame.loadHTMLString_baseURL("", nil)
      post = @posts.arrangedObjects[index]
      frame.loadHTMLString_baseURL(post.text, nil)   

Our second controller is even simpler. This controller has only two outlets, plus two KVC-compliant properties, and a third virtual property.

The sheet outlet provides access to the Add Feed dialog sheet, while the window_controller provides a link back to our main controller.

The name and url properties hold (not surprisingly) the name and URL of the new feed.

Finally, the virtual property, valid_feed, returns true if the feed has a valid name and URL. Obviously, valid_feed depends upon the name and url properties. Key Value Observing will call our valid-feed accessor whenever either of the dependent variables changes.


require 'osx/cocoa'
# AddFeedController acts as the controller for the Add Feed sheet.
class AddFeedController < OSX::NSObject
   ib_outlet :sheet, :window_controller
   kvc_accessor :name, :url
   kvc_depends_on([:name, :url], :valid_feed)
   # Add methods here

The open_dialog() method opens the Add Feed dialog. We declare this as an action, so that we can link it to a button on the main window.


# This action opens the Add Feed sheet.
def open_dialog(sender)
      didEndSelector_contextInfo(   @sheet, 
ib_action :open_dialog

Next, we add our add_feed() action. We will link this action to the Add button on the Add Feed dialog sheet. This method simply converts the feed name and URL into Ruby Strings, then delegates back to the main window controller's add_feed() method. Finally, it closes the Add Feed sheet.


# The add_feed action grabs the name and url from the Add Feed sheet, 
# adds the new feed to the Managed Object Context, then closes the sheet.
def add_feed(sender)
   feed_name = @name.to_s
   feed_url = @url.to_s
   @window_controller.add_feed(feed_name, feed_url)
ib_action :add_feed

The cancel() action simply closes the Add Feed dialog sheet. We will link this action to the Cancel button on the Add Feed sheet.


# The cancel action closes the sheet without adding a new feed.
def cancel(sender)      
ib_action :cancel

The valid_feed() method uses Ruby's regular expressions to filter out invalid entries. Basically, the feed name must contain at least one non-whitespace character, while the URL must start with "feed://", then contain one or more characters, a period, and end with one or more characters. The URL cannot have any white space.

Note: While Ruby has explicit true and false values, it also treats all nil values as false, and all non-nil values as true. This means, the result of ANDing together two regular expressions is either nil or the String matched by the second regular expression. While Ruby will correctly interpret this as a boolean value, when we pass it to the Cocoa framework, we get the following exception:


SetValue_forKey: OSX::OCException: 
NSInternalInconsistencyException - 
  Cannot create BOOL from object <RBObject: 0x1437bdd0> 
  of class RBObject

To prevent this, we explicitly convert our result to a boolean value.


# valid_feed returns true if the Add Feed sheet's current name and url 
# values are valid. This method can be monitored using KVO. 
def valid_feed
   name = @name.to_s
   url = @url.to_s
   result = name.match('\S+') && url.match('^feed://\S+\.\S+$')
   # explicitly convert to booleans.
   return ! result.nil?

Finally, the private helper function, close_dialog() clears the Text Fields and closes the dialog.


# Helper Function: close_dialog clears the Add Feed's 
# text boxes, then closes the sheet.
def close_dialog

Building the User Interface

Building the user interface is also beyond the scope of this article. Simply copy MainMenu.nib from the online source code.

Our user interface consists of the main window and the Add Feed panel. We have three array controllers. The first contains all of our Feed entities. The second contains all Post entities associated with the currently selected Feed. The third contains all Enclosure entities associated with our currently selected Post. Bindings automatically maintain these relationships, requiring no code on our part.

Finally, we have the Add Feed and the Main controllers defined in the previous section.

RubyRSS's Arrays and Controllers

The Main window consists of three Table Views. The first contains the names and post counts from the Feeds array. The second displays titles from the Posts array. The final table contains URLs from the Enclosures array. Again, we set all of these values using Bindings. Of these, only the feed names are editable.

The Main window also has a Web View. This contains the text for the currently selected post; however, unlike the Table Views, we cannot set the Web View's content using Bindings. Instead, we actually have to write code.

The good news is, you've already written this code. Look back at our main controller. Remember, how it receives notifications whenever the posts' selection changes? It then fires the set_html() helper function. That's the code we need. We use KVO to automatically synchronize our web view with the current selection. Basically, we're recreating the code that Bindings normally gives us for free.

Finally, our main window has four Buttons: one adds a new feed, one deletes the selected feed, one sends the selected enclosure to iTunes, and the last one refreshes all our feeds. Since some of these operations can take a long time, we also have a Progress Indicator.

RubyRSS's Main Window

Even simpler, the Add Feed panel has two Text Fields: one for the Feed's name and one for the URL. Each Text Field has a corresponding Label. Finally, we have an Add Button and a Cancel Button.

Add Feed Window

The connections between our UI elements and the controllers' outlets and actions should seem straightforward enough. I won't go into the details here, but I encourage you to open up the nib in Interface Builder and get a feel for the wiring.

One last quick step. Since our user interface uses a Web View, we need to add the WebKit.framework to our project. Right click on the Frameworks folder in the Groups & Files tree. Select Add... Existing Frameworks.... In the File dialog, find and select the WebKit.framework and select Add. In the next panel, just select Add again.

You can now compile and launch the application. Of course, it won't do much yet. We can add new feeds, but we cannot actually read or parse them. All the basic RubyCocoa code works, but we still need to add support for FeedTools and RubyOSA.

Parsing the Feeds

The FeedTools library provides code for parsing, generating and auto discovery of RSS, atom and cdf feeds. We're only using a fraction of its abilities. If you want to know more, check out the web page for FeedTools and its sister project FeedUpdater (

Let's make a new class to handle the interactions between FeedTools and our data model. Create a new Ruby class named FeedReader.rb.


require 'rubygems'
require 'feed_tools'
require 'osx/cocoa'
OSX.require_framework 'CoreData'
# FeedReader class uses feed_tools to download and parse all the feeds, 
# then adds new posts and enclosures to the Managed Object Context.
class FeedReader
   # insert methods here

FeedReader starts by loading the required libraries. Obviously we need FeedTools and RubyCocoa. The RubyGems library lets us access any gem-installed libraries; therefore, we need to load RubyGems before loading FeedTools. Most interestingly, the OSX.require_framework method lets us load Cocoa frameworks. In this case, FeedReader needs Core Data.


# Default Constructor
def initialize(main_controller, moc)
   @main_controller = main_controller
   @moc = moc

Initialize() allows us to construct new FeedReader objects. It takes two arguments: a reference to the main window controller, and a reference to our managed object context.


# Gets the current list of feeds. Downloads all Feeds and adds new Posts 
# and Enclosures to the Managed Object Context.
def refresh
   feed_entries = @main_controller.feeds
   feed_entries.each {|data_feed| update(data_feed)}

FeedReader only exposes a single method to the outside. Refresh() iterates over all the feeds, passing each one to the update() helper function.


# Helper Function: updates a single Feed.
def update(data_feed)
   feed =
   posts = feed.entries
   posts.each{|post| add_post(post, data_feed)}

Update() extracts the list of available posts for each feed. It iterates over the list of posts, calling add_post() for each one.


# Helper Function: adds new posts to the given feed.
def add_post(post, data_feed) 
   title = post.title
   text = post.summary
   # check to see if this already exists...
   return if post_exists?(title, text)
   # now make a new entity
   data_post = OSX::NSEntityDescription\
   data_post.setValue_forKey(title, "title")
   data_post.setValue_forKey(text, "text")
   data_post.setValue_forKey(data_feed, "feed")
   enclosures = post.enclosures
   enclosures.each do |enclosure| 
      add_enclosure(enclosure, data_post)}

Add_post() extracts the post's title and text. It then calls post_exists?(), checking if any posts in the managed object context already have a matching title and text. If the post doesn't already exist, add_post() adds a new Post Entity. It then fills in the entity's attributes and sets the feed relationship.

Since we're using bi-directional relationships, Core Data automatically adds this post to its Feed. Finally, add_post() iterates over the post's list of enclosures, calling add_enclosure() for each one.


# Helper Function: determine if a post exists with the given title and 
# text.
def post_exists?(title, text)
   request = OSX::NSFetchRequest.alloc.init
   description = OSX::NSEntityDescription\
      .entityForName_inManagedObjectContext('Post', @moc)
   predicate = OSX::NSPredicate.predicateWithFormat(
      "title like %@ AND text like %@", title, text)
   error = nil
   count = @moc.countForFetchRequest_error(request, error)
   # if we have an error, assume the post doesn't exist.
   if !error.nil?
      puts "*** Error ***"
      puts error
      return false
   count > 0

Post_exists?() builds an NSFetchRequest for all Post entities whose title and text mach the given arguments. We then use countForFetchRequest_error_() to count the number of matching Posts. For any number greater than zero, we return true. Otherwise, we return false. Note: we log any errors, but simply assume no matches are found.


# Helper Function: adds a single Enclosure to the given Post.
def add_enclosure(enclosure, data_post)
   url = enclosure.url
   isAudio =
   new_enclosure = OSX::NSEntityDescription.\
         "Enclosure", @moc);
   new_enclosure.setValue_forKey(url, "url")
   new_enclosure.setValue_forKey(isAudio, "isAudio")
   new_enclosure.setValue_forKey(data_post, "post")

Finally, add_enclosure() adds a new Enclosure entity to the managed object context. It then fills the attributes, and sets the post relationship. Again, we have a bi-directional relationship, so Core Data automatically adds this Enclosure to the Post's enclosures relationship.

Now we just need to make our MainController aware of the FeedReader. Add the following line to MainController's awakeFromNib() method:

@feed_reader =, @app_delegate.managedObjectContext)

This creates a new FeedReader object. We just need to call @feed_reader.refresh() whenever the user presses the Refresh Feeds button. However, this operation can take a while, especially when you subscribe to a lot of feeds. We don't want our UI to freeze up. Also, we would like to let the user know that something is actually happening. So, let's have FeedReader refresh the feeds in a second thread, and turn on the progress bar.

Unfortunately, Ruby 1.8 is not thread safe. We cannot call Ruby code from a second (or third, or fourth...) Objective-C thread. However, we can use Ruby's internal threads. Ruby uses a green threading model. Basically, as far as the hardware knows, Ruby runs in a single thread, but the Ruby interpreter can time slice between several green threads.

Ruby threads have some advantages and some disadvantages over processor threads. A full discussion is beyond the scope of this article, but — bottom line — they work fine for our purposes. The user interface will not freeze up while we refresh the feeds.

Simply replace refreshFeedsAction() with the following code.

new refreshFeedsAction()

# This action refreshes all the feeds.
def refreshFeedsAction(sender) do
      rescue Exception => e
         puts e.message
ib_action :refreshFeedsAction

The begin. . .rescue. . .end blocks handle any exceptions thrown in our worker thread. If an error occurs, it executes the rescue block, which logs the error and quits the application.

I'm a firm believer in failing fast. We don't want our code to lumber ahead in an unknown state. At least during development, go ahead and force the application to stop as soon as an error occurs.

That's it. Open up the RSS reader and add a few feeds. Click the Refresh Feeds button, and watch the posts roll in. Quit the application, and then launch it again. Core Data automatically saves all our data.

The Complete RubyRSS

This almost looks like a real application. Almost. It still needs a lot of work. For example, the tables remain completely unsorted. Ideally, users will want to filter them as well. By default we should probably filter out any posts that the user has already read. Searching would also be nice.

From a software engineering standpoint, we're playing fast and loose with our threads here. The user can perform any number of bad actions (like quitting the application) while the worker thread is still running. We should probably address that.

But, you have to admit, we squeezed a ton of functionality out of a few hundred lines of code. Ruby + Core Data + Bindings makes a high-octane combination.

Next time, we will look at using RubyOSA to send enclosures to iTunes. We will also take a look at RubyCocoa's debugging options, and look at a few other cool tricks as well.

Rich Warren lives in Honolulu, Hawaii with his wife, Mika, daughter, Haruko, and his son, Kai. He is a software engineer, freelance writer and part time graduate student. When not playing on the beach, he is probably writing, coding or doing research on his MacBook Pro. You can reach Rich at, or check out his blog at


Community Search:
MacTech Search:

Software Updates via MacUpdate

Duet - Use your iPad as an exter...
Duet is the first app that allows you to use your iDevice as an extra display for your Mac using the Lightning or 30-pin cable. Note: This app requires a $14.99 iOS companion app. Version Read more
iExplorer 4.1.5 - View and transfer file...
iExplorer is an iPhone browser for Mac lets you view the files on your iOS device. By using a drag and drop interface, you can quickly copy files and folders between your Mac and your iPhone or... Read more
Alarm Clock Pro 10.3 - $19.95
Alarm Clock Pro isn't just an ordinary alarm clock. Use it to wake you up in the morning, send and compose e-mails, remind you of appointments, randomize the iTunes selection, control an internet... Read more
Tor Browser Bundle 7.0.7 - Anonymize Web...
The Tor Browser Bundle is an easy-to-use portable package of Tor, Vidalia, Torbutton, and a Firefox fork preconfigured to work together out of the box. It contains a modified copy of Firefox that... Read more
Data Rescue 5.0.1 - Powerful hard drive...
Data Rescue’s new and improved features let you scan, search, and recover your files faster than ever before. We have modernized the file-preview capabilities, added new files types to the recovery... Read more
Alfred 3.5.1 - Quick launcher for apps a...
Alfred is an award-winning productivity application for OS X. Alfred saves you time when you search for files online or on your Mac. Be more productive with hotkeys, keywords, and file actions at... Read more
Tunnelblick 3.7.3 - GUI for OpenVPN.
Tunnelblick is a free, open source graphic user interface for OpenVPN on OS X. It provides easy control of OpenVPN client and/or server connections. It comes as a ready-to-use application with all... Read more
DEVONthink Pro 2.9.16 - Knowledge base,...
Save 10% with our exclusive coupon code: MACUPDATE10 DEVONthink Pro is your essential assistant for today's world, where almost everything is digital. From shopping receipts to important research... Read more
AirRadar 4.0 - $9.95
With AirRadar, scanning for wireless networks is now easier and more personalized! It allows you to scan for open networks and tag them as favourites or filter them out. View detailed network... Read more
ForkLift 3.0.8 Beta - Powerful file mana...
ForkLift is a powerful file manager and ferociously fast FTP client clothed in a clean and versatile UI that offers the combination of absolute simplicity and raw power expected from a well-executed... Read more

Wheels of Aurelia (Games)
Wheels of Aurelia 1.0.1 Device: iOS Universal Category: Games Price: $3.99, Version: 1.0.1 (iTunes) Description: | Read more »
Halcyon 6: Starbase Commander guide - ti...
Halcyon 6 is a well-loved indie RPG with stellar tactical combat and some pretty good writing, too. It's now landed on the App Store, so mobile fans, if you're itching for a good intergalactic adventure, here's your game. Being a strategy RPG, the... | Read more »
Game of Thrones: Conquest guide - how to...
Fans of base building games might be excited to know that yet another entry in the genre has materialized - Game of Thrones: Conquest. Yes, you can now join the many kingdoms of the famed book series, or create your own, as you try to conquer... | Read more »
Halcyon 6: Starbase Commander (Games)
Halcyon 6: Starbase Commander Device: iOS Universal Category: Games Price: $6.99, Version: (iTunes) Description: An epic space strategy RPG with base building, deep tactical combat, crew management, alien diplomacy,... | Read more »
Legacy of Discord celebrates its 1 year...
It’s been a thrilling first year for fans of Legacy of Discord, the stunning PvP dungeon-crawling ARPG from YOOZOO Games, and now it’s time to celebrate the game’s first anniversary. The developers are amping up the festivities with some exciting... | Read more »
3 reasons to play Thunder Armada - the n...
The bygone days of the Battleship board game might have past, but naval combat simulators still find an audience on mobile. Thunder Armada is Chinese developer Chyogames latest entry into the genre, drawing inspiration from the explosive exchanges... | Read more »
Experience a full 3D fantasy MMORPG, as...
Those hoping to sink their teeth into a meaty hack and slash RPG that encourages you to fight with others might want to check out EZFun’s new Eternity Guardians. Available to download for iOS and Android, Eternity Guardians is an MMORPG that lets... | Read more »
Warhammer Quest 2 (Games)
Warhammer Quest 2 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: Dungeon adventures in the Warhammer World are back! | Read more »
4 of the best Halloween updates for mobi...
Halloween is certainly one of our favorite times for mobile game updates. Many popular titles celebrate this spooky season with fun festivities that can stretch from one week to even the whole month. As we draw closer and closer to Halloween, we'... | Read more »
Fire Rides guide - how to swing to succe...
It's another day, which means another Voodoo game has come to glue our hands to our mobile phones. Yes, it's been an especially prolific month for this particular mobile publisher, but we're certainly not complaining. Fire Rides is yet another... | Read more »

Price Scanner via

Apple restocks full line of refurbished 13″ M...
Apple has restocked a full line of Apple Certified Refurbished 2017 13″ MacBook Pros for $200-$300 off MSRP. A standard Apple one-year warranty is included with each MacBook, and shipping is free.... Read more
13″ 3.1GHz/256GB MacBook Pro on sale for $167...
Amazon has the 2017 13″ 3.1GHz/256GB Space Gray MacBook Pro on sale today for $121 off MSRP including free shipping: – 13″ 3.1GHz/256GB Space Gray MacBook Pro (MPXV2LL/A): $1678 $121 off MSRP Keep an... Read more
13″ MacBook Pros on sale for up to $120 off M...
B&H Photo has 2017 13″ MacBook Pros in stock today and on sale for up to $120 off MSRP, each including free shipping plus NY & NJ sales tax only: – 13-inch 2.3GHz/128GB Space Gray MacBook... Read more
15″ MacBook Pros on sale for up to $200 off M...
B&H Photo has 15″ MacBook Pros on sale for up to $200 off MSRP. Shipping is free, and B&H charges sales tax in NY & NJ only: – 15″ 2.8GHz MacBook Pro Space Gray (MPTR2LL/A): $2249, $150... Read more
Roundup of Apple Certified Refurbished iMacs,...
Apple has a full line of Certified Refurbished 2017 21″ and 27″ iMacs available starting at $1019 and ranging up to $350 off original MSRP. Apple’s one-year warranty is standard, and shipping is free... Read more
Sale! 27″ 3.8GHz 5K iMac for $2098, save $201...
Amazon has the 27″ 3.8GHz 5K iMac (MNED2LL/A) on sale today for $2098 including free shipping. Their price is $201 off MSRP, and it’s the lowest price available for this model (Apple’s $1949... Read more
Sale! 10″ Apple WiFi iPad Pros for up to $100...
B&H Photo has 10.5″ WiFi iPad Pros in stock today and on sale for $50-$100 off MSRP. Each iPad includes free shipping, and B&H charges sales tax in NY & NJ only: – 10.5″ 64GB iPad Pro: $... Read more
Apple iMacs on sale for up to $130 off MSRP w...
B&H Photo has 21-inch and 27-inch iMacs in stock and on sale for up to $130 off MSRP including free shipping. B&H charges sales tax in NY & NJ only: – 27″ 3.8GHz iMac (MNED2LL/A): $2179 $... Read more
2017 3.5GHz 6-Core Mac Pro on sale for $2799,...
B&H Photo has the 2017 3.5GHz 6-Core Mac Pro (MD878LL/A) on sale today for $2799 including free shipping plus NY & NJ sales tax only . Their price is $200 off MSRP. Read more
12″ 1.2GHz Space Gray MacBook on sale for $11...
Amazon has the 2017 12″ 1.2GHz Space Gray Retina MacBook on sale for $100 off MSRP. Shipping is free: 12″ 1.2GHz Space Gray MacBook: $1199.99 $100 off MSRP Read more

Jobs Board

Product Manager - *Apple* Pay on the *Appl...
Job Summary Apple is looking for a talented product manager to drive the expansion of Apple Pay on the Apple Online Store. This position includes a unique Read more
*Apple* Retail - Multiple Positions - Farmin...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Frameworks Engineer, *Apple* Watch - Apple...
Job Summary Join the team that is shaping the future of software development for Apple Watch! As a software engineer on the Apple Watch Frameworks team you will Read more
*Apple* News Product Marketing Mgr., Publish...
Job Summary The Apple News Product Marketing Manager will work closely with a cross-functional group to assist in defining and marketing new features and services. Read more
Fraud Analyst, *Apple* Advertising Platform...
Job Summary Apple Ad Platforms has an opportunity to redefine advertising on mobile devices. Apple reaches hundreds of millions of iPhone, iPod touch, and iPad Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.