Mastodon

RubyMotion gotchas (1)

When using RubyMotion, just like many other new technologies or frameworks, I found gotchas and scratch my head over them.
I'm writing them down in case I might forget them. I hope this may help you or give you ideas on daily issues you might face when using RubyMotion.

When class is not class

Try to convert following Objective-C code into RubyMotion code:

[self.collectionView registerClass:[MyCell class] forCellWithReuseIdentifier:@"MY_CELL"];

Quite straight forward, isn't it?

self.collectionView.registerClass(MyCell.class, forCellWithReuseIdentifier:"MY_CELL")

Nope. The above code will crash in runtime with following error:

2013-01-28 16:30:43.794 test_collection_view[48487:c07] +[RBAnonymous5 initWithFrame:]: unrecognized selector sent to class 0x76d0950

That is wired, but it is not that wired if we think again.

In Objective-C, there is a "+class" class method in NSObject. As its name suggested, it return the class object of the specifing class. For example, [UIView class] return UIView class object.

In Ruby, there is a "#class" instance method in Object. When you called this on an instance of object, it return the class of that object. For example, my_view.class return UIView if my_view is an instance of UIView. Note there are no class method "::class" on Object on Ruby. Why you can still call something like "Time.class" in ruby? It is because any class in Ruby is actually instance of Class.

This explain why MyCell.class in RubyMotion is different from [MyCell class] in Objective-C. Instead of returning class "MyCell", MyCell.class returning the class of MyCell class.

To solve the issue, you can use the Ruby way:

self.collectionView.registerClass(MyCell, forCellWithReuseIdentifier:"MY_CELL")

Or the Objective-C way:

self.collectionView.registerClass(NSClassFromString("MyCell"), forCellWithReuseIdentifier:"MY_CELL")

Both way correctly pass the class "MyCell" to the registerClass:forCellWithReuseIdentifier: method.

When nil is not NULL

In Objective-C (inherited from C), NULL is macro point to null-pointer constant. It is a special value that indicates that the pointer is not pointing to any object.

NULL is a value, and you cannot adds value to Objective-C collection. Therefore they created null object [NSNull null] to represent "nothing" in collection.

In Ruby, nil is a singleton object which represent "nothing". Naturally RubyMotion inherited this. To make it simple to work on Objective-C collection, [NSNull null] returns nil.

The problem is while Ruby API only recognize nil, various Cocoa API use NULL as a special value. In RubyMotion, there are no way you could specify NULL value.

If you see code like this in Objective-C:

if (val == NULL) {
  // do something
} else {
  // do something else
}

They might not work as you would have expected in RubyMotion.

One of the example is the UICollectionView API

deselectItemAtIndexPath:animated:

Deselects the item at the specified index.

  • (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated Parameters

indexPath
The index path of the item to select. Specifying nil for this parameter removes the current selection.

animated
Specify YES to animate the change in the selection or NO to make the change without animating it.

In short, in Objective-C:

[collectionView deselectItemAtIndexPath:NULL animated:YES];

will removes the current selection on the collection view.

In RubyMotion however:

collection_view.deselectItemAtIndexPath(nil, animated:true)

will not work.

Remember nil is NOT NULL. When using RubyMotion, you must aware that you have NO way to specify NULL, and be prepared to workaround Cocoa API that expects NULL.

References