Steve's Ruby Motion Blog...

...about programming and whatever else comes into my head.

 

January 30th 2013

RubyMotion Techniques: A Few Nuggets

RubyMotion is a powerful implementation of Ruby on top of Apple’s iOS framework (it’s hard for me to write a good descriptor for this — is it Cocoa or is it iOS? But you get the idea). People travel many paths to get to RubyMotion and a very frequent one is the “I know Ruby so now I can write iPhone apps” path. The other direction from which people arrive is “I know Objective-C and now I’d like to use RubyMotion instead.” This post is directed more toward the former group and suggests a preamble and then some tips on what works for me and what doesn’t. If you have had different experiences, please let me know in a comment.

RubyMotion Is Not A Framework

If you are a Rubyist, you probably have experience with Rails, Sinatra, or one of the other frameworks for server-side app development. RubyMotion is not like this at all. The framework is the underlying iOS API. What that means to you, the developer, is that you will not be able to forget about all that annoying Objective-C stuff — it’s not abstracted away for you. You will eventually need to read some Objective-C and most Q & A about iOS development techniques is couched in terms of Objective-C. So, in no particular order, here we go with my tips:

Learn to Read (and maybe write) Objective-C

I know the whole point of getting RubyMotion was to not use Objective-C, but you are still working in an environment that was carefully crafted with Objective-C in mind. Take, for example Cocoapods. These are analogous to Ruby Gems, but for Cocoa projects. They are written in Objective-C, and seldom documented extensively. To know what methods and properties a given pod exposes, you will have to read the Objective-C — at least in the headers — the .h files located in vendor/Pods/YourPodName. Don’t get me wrong — some of the pods have great documentation, but there are a number of very useful ones that you just will not be able to use without reading the source.

Another example is the #lazyprogrammer one. I’m being snarky here because it’s something I do sometimes as well. When I know I’m way outside the “It’s a RubyMotion” purview and I’ve spent quite enough time running down rabbit holes with Google, I may post a question on StackOverflow. But I don’t post it with the tag RubyMotion because a lot of the Cocoaheads will just ignore it. I post it with the keywords:

iOS, whatever, whatever

I also post all code translated from Ruby to Objective-C.

So, for example, if I were asking a question about customizing a UITableCellView I would expect to get a ton of “just create a separate NIB and blah, blah, blah” answers, which would be perfectly on point, except that I want a programmatic example. So I might say: “I am creating a subclass of UITableCellView programmatically — I know I can do this in IB but…”, which winnows out the (probably correct) answers about how to accomplish the task with IB.

RubyMotion Still Requires You to Learn iOS

The more you know about iOS, the more your code will look right and work right. There are established conventions — some from Apple and some from people who’ve been doing this a while and know things about things. Here’s an example:

Initializing A Subclass of an iOS Class

It’s necessary in many cases to write subclasses of the standard iOS classes. For example:

class MyFineTableViewController < UITableViewController
  def initialize(some_variable)
    @some_variable = some_variable
  end
end

Then in your calling code, you use a Rubyish sequence like:

@my_controller = MyFineTableViewController.new('hello')

This seems pretty straightforward but it’s not the way iOS classes initialize themselves. You’re better off doing something along the lines of:

class MyFineTableViewController < UITableViewController
  def initWithTitleString(title_string)
    initWithNibName nil, bundle:nil
    @title_string = title_string
    self
  end
end

Then in your calling sequence, you use something like:

@my_controller = MyFineTableViewController.alloc.initWithTitleString('hello')

This Is Important: If you use the alloc.initWithFoo initialization paradigm, you need to call the superclass’s designated initializer and (read carefully) you need to return self. Failure to return self will cause you cryptic and evil errors that will cost you many gray-matter cycles to decipher.

Initializing Your Own Ruby Classes

There’s no need to follow the alloc.init paradigm for pure Ruby objects. You can use the Ruby constructor method initialize as you normally would, and code like: MyModel.new(:name => 'yojimbo') to initialize. This also goes for classes that RubyMotion wraps like NSString, NSMutableArray, etc. As long as you are using the Ruby initialization, you’re ok.

an_array = Array.new
# or
another_array = []

these work just perfectly. As does:

an_array = NSMutableArray.arrayWithCapacity(10)
# or
an_array = NSMutableArray.alloc.initWithCapacity(10)

So many ways to achieve the same thing…

Mixins or Inheritance

One thing Ruby brings into the iOS programming world is a straighforward way to mix functionality into your classes. But let me back up to inheritance because it’s pretty straightforward to understand.

Inheritance is how you derive a specialization from an iOS class. So, for example, PeopleController is a kind of UITableViewController, which makes sense. PeopleController implements whatever is specific to people and lets UITableViewController provide the rest. This is a typical single inheritance use of OOP. So, in code, you write:

class PeopleController < UITableViewController
  # more stuff
end

Mixins are a different kettle of fish, so to speak. They allow you to mix functionality directly into the namespace of your class. I’m using it in MotionModel. Here’s an example:

class Person
  include MotionModel::Model
  include Validatable
  include MotionModel::Formotion

  # other stuff
end

I’m not going to talk about writing mixins here except to remark that while inheritance observes an “is_a?” relationship with a base class, mixins implement more of an “acts-as” relationship. In the foregoing example, by including MotionModel::Model I have a class that acts like a model. And by including Validatable, the class is further extended also to act like it is validatable. The point is that it can act like a number of different things, not just its base class.

On a personal note, I’ve found mixins enormously productive in refactoring code because I am not inheriting from a single monolithic base class that has all the code I’ve pulled out of subclasses. Rather, each refactoring goes in a module I can mix in. In my current project I have modules for IAd, BarButton, ViewHelpers, and so on. Not all controllers show ads, so I don’t mix that in when I’m not showing an ad. Similarly, not every controller needs to add custom UIBarButtonItems, so I only mix that in where I need it. That’s my personal preference. I could just as easily have refactored the code into a set of base classes like:

Person < FormotionWithValidationsModel < ValidatableModel < BaseModel

This strategy works too and is, in some cases, much easier to implement if you know the contracts your classes have and services they need to provide. Rails is implemented (at the user level) on a single-inheritance model for the most part. A good portion the magic stuff is done in mixins, though.

Reopening Classes

This is one of the coolest parts of Ruby. All classes are open and can be modified at runtime. Code that changed like this used to give me the shivers but now that I’ve used Ruby for a number of years, I’ve come to understand that in most cases it works just fine. Of course, if you plan to do this and share it, remember not to violate the priciple of least surprise. Especially if you override a method in a built-in class.

Method Signatures

Method signatures in plain ol' Ruby are easy to understand. Either the method is defined or it isn’t. Either it has the right number of arguments in the invocation or it doesn’t. But in RubyMotion it’s a bit different. You might implement the delegate method:

def tableView(tableView, editingStyle, indexPath)

and wonder why the heck it isn’t being called. Well, it’s an honest mistake. The method signature for the delegate method is:

tableView:commitEditingStyle:forRowAtIndexPath:

All this punctuation is significant. If you see a colon (:), it means “stick the receiving argument right here.” Inside iOS, when UIKit is figuring out whether to apply some kind of edit based on tapping an accessory, it will call this method by sending a selector. The selector is implemented in RubyMotion exactly conforming to it’s Objective-C signature. So it’s:

def tableView(tableView,
              commitEditingStyle:style,
              forRowAtIndexPath:indexPath)

Notice how each colon translated into a receiving argument?

What I often do when translating the Objective-C to Ruby is this:

  1. Paste the declaration from the documentation into my code:
- (void)tableView:(UITableView *)tableView
          commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
           forRowAtIndexPath:(NSIndexPath *)indexPath
  1. Turn it from an Objective-C instance method (the - in the above line) to Ruby.
def tableView:(UITableView *)tableView
          commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
           forRowAtIndexPath:(NSIndexPath *)indexPath
  1. Remove the first colon and the return type.
def tableView tableView
          commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
          forRowAtIndexPath:(NSIndexPath *)indexPath
  1. Insert commas between the arguments and remove the type information.
def tableView tableView,
              commitEditingStyle:editingStyle,
              forRowAtIndexPath:indexPath
  1. Optionally, parenthesize the argument list for readability.
def tableView(tableView,
              commitEditingStyle:editingStyle,
              forRowAtIndexPath:indexPath)

By doing this I save myself the time of guessing at method signatures and wondering why stuff doesn’t get called. As I find methods that are more frequently called, I wind up not doing this because I know the parameters, but you can see how this is useful when using some obscure set of methods for the first time. Also, check out the RubyMotion docset for Dash.

Only Observe What’s Yours to Observe

In iOS, it’s very efficient to subscribe/observe or “listen” for events. This works great until you forget to unhook one of the observers. If you set up an observer, make sure it is unhooked before the object is destroyed or you have a potential leak. It’s not clear how adversely forgetting to unhhok affects performance, but you can see how this kind of thing would build up if a number of controllers are watching events and running code on them even if it’s not appropriate to do so.

Live the Lifecycle

Views have a very specific lifecycle and their controllers are called at each stage. Here are some interesting ones:

  • init — ‘nuff said. It’s the designated initializer. This is called when any object derived from NSObject is instantiated

  • viewDidLoad — Called when iOS has successfully loaded a view but not yet displayed it

  • viewWillAppear — Called when iOS is about to load a view but before it has displayed it

  • viewDidAppear — Called when the view successfully loaded

  • viewWillDisappear — Called when iOS is about to hide a view but hasn’t yet

  • viewDidDisappear — Called when iOS has hidden the view

  • viewDidUnload — Called when iOS unloads the view

From my perspective, the best reason to use the callbacks during the presentation of the view are to:

  • Do any styling you might need

  • Add any subviews you require

  • Hook up any observers

The callbacks when the view disappears are often used to:

  • Stop observing

  • Free resources if you have any locked down

Typically, if you added a subview in an Objective-C program, you would remove it during teardown and then set it to nil so it could be reaped (or you could explicitly release it). ARC mitigates this and you should not have to manually maintain the parallelism between adding and removing views or instantiating/destroying objects.

Well, that’s about it for today. I just wanted to put a tips I’ve gathered from writing RubyMotion code down in writing and I hope they’re helpful.

blog comments powered by Disqus