RubyMotion promises to bring the clarity and concise syntax of Ruby to iOS and OS X development. ReactiveCocoa’s aim is to help reduce complexity by deriving state instead of declaring it. Sounds like a great combination, right?

There are some complications. First, the documentation and examples almost all use the RAC and RACAble macros provided by ReactiveCocoa, something we can’t use from RubyMotion. Bummer. And RubyMotion’s bridgesupport doesn’t allow us to call Objective-C methods that take blocks typed as id. Rats. Fortunately these can be overcome, thanks to some helpful folks on the internet, so let’s get started.



1. Install ReactiveCocoa

ReactiveCocoa is available through CocoaPods. Update your RubyMotion project’s Rakefile to include it:

Motion::Project::App . setup do | app | ... app . pods do pod 'ReactiveCocoa' end ... end Motion::Project::App.setup do |app| ... app.pods do pod 'ReactiveCocoa' end ... end

The next time you build your project, CocoaPods will retrieve and build the ReactiveCocoa library. There are RAC extensions for many popular libraries. I recommend checking out AFNetworking-RACExtensions in particular.

2. Set Up a Shim for RubyMotion

We ran across an example project from Dave Lee. He sets up a convenient shim that solves both of the complications I mentioned in the opening. There are two primary pieces.

On line 84 of IMMViewController.rb a new class method, reduceLatest , is added to the RACSignal class. This method wraps the existing combineLatest to resolve the problem of passing blocks as type id . It works in conjunction with an Objective-C shim and .bridgesupport file that enumerate methods taking blocks of specific arity (1-5 in this case, add more if you need them).

The other piece, starting at line 159 of IMMViewController.rb, is a replacement for the convenient RAC and RACAble macros. Keep in mind that objects passed to the rac method need to be KVO-compliant.

Overall, lines 77 – 219 of IMMViewController.rb and the contents of the vendor/ReactiveMotion make a pretty good shim.

3. Read the Docs

ReactiveCocoa’s paradigms can be challenging to get a good grasp on. Fortunately the team has provided good documentation. Read it. In particular, I’d recommend starting with the Readme and the Framework Overview.

Justin DeWind has a nice collection of resources in his post, ReactiveCocoa: The Future of Cocoa Programming

I’ve also had a great experience diving into ReactiveCocoa’s source code.

Example: Triggering Authentication Using a Command

This is an example of using a command to start the authentication process.

class LoginViewController < UIViewController attr_accessor :credentials def viewDidLoad super @credentials = RACSignal. reduceLatest ( usernameField. rac_textSignal , passwordField. rac_textSignal ) do | user, pass | @_credentials = [ user, pass ] end end # Wired up to a button in our storyboard def signInClicked @command . execute ( @_credentials ) end end # elsewhere, in a class that has a reference to the # LoginViewController (@view) and user model (@user) class LoginManager attr_accessor :credentialsAreNotEmpty ... # Gets triggered after the view for the view controller above is loaded def configure @credentialsAreNotEmpty = @view . credentials . map -> ( creds ) do creds. length == 2 and !creds. any ? { | c | c. nil ? or c. empty ? } end @authCommand = RACCommand. commandWithCanExecuteSignal credentialsAreNotEmpty @authCommand . allowsConcurrentExecution = false # The command's `executing` will remain YES until the # signal returned by the block completes or errors. @authCommand . addSignalBlock -> ( credentials ) do # User#authenticate uses AFNetworking-RACExtensions under the hood and # returns a signal that completes when the auth request is complete. @user . authenticate credentials end end end class LoginViewController < UIViewController attr_accessor :credentials def viewDidLoad super @credentials = RACSignal.reduceLatest(usernameField.rac_textSignal, passwordField.rac_textSignal) do |user, pass| @_credentials = [user, pass] end end # Wired up to a button in our storyboard def signInClicked @command.execute(@_credentials) end end # elsewhere, in a class that has a reference to the # LoginViewController (@view) and user model (@user) class LoginManager attr_accessor :credentialsAreNotEmpty ... # Gets triggered after the view for the view controller above is loaded def configure @credentialsAreNotEmpty = @view.credentials.map -> (creds) do creds.length == 2 and !creds.any? {|c| c.nil? or c.empty? } end @authCommand = RACCommand.commandWithCanExecuteSignal credentialsAreNotEmpty @authCommand.allowsConcurrentExecution = false # The command's `executing` will remain YES until the # signal returned by the block completes or errors. @authCommand.addSignalBlock ->(credentials) do # User#authenticate uses AFNetworking-RACExtensions under the hood and # returns a signal that completes when the auth request is complete. @user.authenticate credentials end end end

One thing I’d love to find a better way to accomplish is aggregating the command execution with the stream of current credentials. Keeping the added state @_credentials is gross. I tried a few variations of reduceLatest but didn’t get the behavior I was looking for.

