I’m writing an OS X GUI test runner for Ruby. It’s coming along nicely, and I’ll have something to show fairly soon. In the meantime, I’m going to share some things I discovered in the process of writing it.

First, DRb (Distributed Ruby) doesn’t play well with Cocoa objects, due to incompatibilities between Ruby’s threading and Cocoa’s calling model. Sometimes it works; mostly, it results in segmentation faults.

Instead of DRb, I decided to use NSDistributedNotificationCenter, OS X’s own distributed notification system. I couldn’t find many examples of usage on the internet. There were zero server examples in Ruby, and one in Python. After a bit of trial and error, I got it working; hopefully Google and this page will save someone else the trouble.

Here’s some sample server code:

require 'osx/cocoa'

include OSX

class Server < NSObject
  def foo(notification)
    p notification_to_hash(notification)
  end
  
  def notification_to_hash(notification)
    nsd = notification.userInfo
    return nsd.allKeys.to_a.inject({}){ |hash, oc_key| 
      hash[oc_key.to_s] = nsd.objectForKey(oc_key).to_s
      hash
    }
  end
end

server = Server.alloc.init

center = NSDistributedNotificationCenter.defaultCenter
center.addObserver_selector_name_object(
  server,
  "foo:", 
  "My Notification",
  "com.example.MyApp"
)

NSRunLoop.currentRunLoop.run

And the client:

require 'osx/cocoa'

include OSX

center = NSDistributedNotificationCenter.defaultCenter
center.postNotificationName_object_userInfo_deliverImmediately(
  "My Notification",
  "com.example.MyApp",
  {"foo" => "bar", "baz" => 3},
  true
)

It’s generally quite simple, but it’s important that the server object be an instance of an NSObject (or one of its descendants). Plain Ruby objects cause bus errors.