reality

on web, mobile and software

TOKYO Rails #19

| Comments

During our trip in Tokyo last month, I and my wife attended TOKYO Rails #19 at COOKPAD, and that is fantastic.

While I don’t actually coding Rails lately, I’m lucky to met a few iOS developers and exchanged a bit, such as introducing もじバトル, SplitCam and EverClip, and see some interesting in house app as well as a creative camera app Cobypic.

And oh, the event venue COOKPAD office is super cool. Their guys kindly made us a fantastic meal. Sorry that I can only bring you a few pictures:

I can’t made it to their iOS meetup… maybe next time!

RubyMotion Gotchas (1)

| Comments

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:

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

Quite straight forward, isn’t it?

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

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

1
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:

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

Or the Objective-C way:

1
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:

1
2
3
4
5
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:

1
[collectionView deselectItemAtIndexPath:NULL animated:YES];

will removes the current selection on the collection view.

In RubyMotion however:

1
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

Error Decoding Msgpack Data: Invalid Byte Sequence in UTF-8

| Comments

My new app use msgpack to encode data before sending to server. On server side, its a Sinatra app that decode and store the data to database.

The app works fine until I push real data. With real data the app crash with error “invalid byte sequence in UTF-8”.

After some lengthy investigation, I found the data I sent to server is decoded incorrectly. The offending code look like this:

1
unpacked = MessagePack.unpack(data)

What could possibly gone wrong?

Turns out as discussed here, msgpack is a binary serialization format, and it expects to unpack from a raw binary string. You need to force the data string (from HTTP POST request) to binary encoding.

1
MessagePack.unpack(data.force_encoding(Encoding::BINARY))

Now msgpack unpack the data properly.

P.S. If you use JRuby and msgpack-jruby, beware another issue that, msgpack-jruby behave differently than the MRI version. It will not use default_external encoding, but you will need to explicitly specify the encoding during unpack. (As discussed here)

Search Your Git History

| Comments

Wonder how to search your entire git repo using grep? This command will work:

1
git grep <regexp> $(git rev-list --all)

It search and returns every file contains that regular expression in your repo. Certainly it is slow, but sometime you will have to do it.

Using DTrace With RubyMotion

| Comments

Want to profile or analyze your RubyMotion application? How to use Instrument with motion is not yet known, but you can always use powerful DTrace to do that.

Turns out running DTrace in RubyMotion is exactly same as that in MacRuby (no surprise!). motion-dtrace is a proof of concept to simplify running dtrace with your app.

Installation

1
gem install motion-dtrace

Usage

Edit the Rakefile of your RubyMotion project and add the following require line.

1
2
require 'rubygems'
require 'motion-dtrace'

Start simulator process:

1
rake

On another terminal, start dtrace:

1
rake dtrace

When you terminate the app, you get a trace similar to following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
                     CLASS#METHOD                          TOTAL TIME µsec
--------------------------------------------------------------------------------
                   CGPoint#y=:                             3
                    String#nil?                            3
                     Array#last                            4
                    CGRect#origin                          4
            Evernote::Note#title                           4
                      Hash#empty?                          4
                      ........
                     Class#all:                            19867
                   UIImage#thumbnail                       24226
              DTWebArchive#thumbnail                       24415
                  ClipItem#thumbnail                       24451
                __NSArrayM#each                            24717
      ClipperTableViewCell#item=:                          25143

Extra

You may specify your own dtrace file via:

1
rake dtrace DTRACE=/Users/siuying/Documents/workspace/motion/motion-dtrace/dtrace/methods_duration.d

TODO

Currently this gem is just a hack to simplify command lines.

We shall investigate can we do real integration with project.

Credit

Create Gems for RubyMotion

| Comments

While RubyMotion featured using rubygems to extend projects, the actual steps to do so is not documented until motion-redgreen gems demonstrate it.

Define A Gem

Making gem for Motion is not unlike that for standard gems, with some exceptions.

To add a gem to your RubyMotion project, instead of using rubygems to require source files in runtime, you require it in project Rakefile.

Inside the source file being required, instead of require other files normally you would in rubygems, use Motion::Project::App.setup to add sources to your motion project:

1
2
3
4
5
6
7
8
9
unless defined?(Motion::Project::Config)
  raise "This file must be required within a RubyMotion project Rakefile."
end

Motion::Project::App.setup do |app|
  Dir.glob(File.join(File.dirname(__FILE__), 'nano_store/*.rb')).each do |file|
    app.files.unshift(file)
  end
end

Adding Source Files

You can add files to the project with app.files:

1
2
3
4
5
Motion::Project::App.setup do |app|
  Dir.glob(File.join(File.dirname(__FILE__), 'bubble-wrap/*.rb')).each do |file|
    app.files.unshift(file)
  end
end

Adding Libraries, Frameworks or External Projects

Add libraries, framework or external projects as you normally would:

1
2
3
4
5
6
7
8
9
Motion::Project::App.setup do |app|
  Dir.glob(File.join(File.dirname(__FILE__), 'motion-hpple/*.rb')).each do |file|
    app.files.unshift(file)
  end
  app.libs << '/usr/lib/libxml2.2.dylib'

  hpple_vendor = File.expand_path(File.join(File.dirname(__FILE__), '../vendor/hpple'))
  app.vendor_project(hpple_vendor, :static)
end

Adding Cocoapods

Add cocoapods dependency with following code:

1
2
3
4
5
6
7
8
Motion::Project::App.setup do |app|
  Dir.glob(File.join(File.dirname(__FILE__), 'nano_store/*.rb')).each do |file|
    app.files.unshift(file)
  end

  app.pods ||= Motion::Project::CocoaPods.new(app)
  app.pods.dependency 'NanoStore', '~> 2.0.1'
end

If the project already using cocoapods, above code will just work. If you have not include any other pods in the project, add following lines to the project Rakefile:

1
2
3
4
5
6
7
8
Motion::Project::App.setup do |app|
  app.name = 'myapp'

  # Only needed if you have not already specifying a pods dependency
  app.pods do
    dependency 'NanoStore', '~> 2.0.1'
  end
end

Conclusion

Now you are ready to roll. Have a look of following RubyMotion gems if you need more help:

[REPOST] 登記成為「資訊科技界」功能組別選民

| Comments

作為 IT 界一員,你是否常感到 IT 界功能組別的代表未能 反映 你的 觀點呢?為了不讓悲劇重演,你可以登記做選民,為自己和業界投重要的一票。

據「立法會條例 20Z 資訊科技界功能界別的組成」,選民必需是指定學會和團體的會員。 根據我的調查其中 BCSIEEE 手續較簡便。以下是我的筆記:

注冊成為 IEEE 會員以及登記做選民

  1. 開啟 IEEE 網站,選擇 “Join IEEE”

  2. 請先到 qualitications 確認自己已有 Professional Membership 的資格,然後選擇 Professional Membership,按 ” Begin join Process”。

  3. 網頁會自動跳到 IEEE Shop 網站。用網站的戶口登入,或開啟一個新戶口。注意地址需要是在「香港」地區才能加入香港分會。

  4. 登入後會自動加入 “IEEE Membership” 。要再新增一個合資格投票的 Society:IEEE Circuits & Systems Society, IEEE Communications Society 或 IEEE Computer Society 其中一個便行。

  5. IEEE 會員以年為單位,每年12月31日到期。在下半年申請,可免一半年費。例如加入 IEEE Membership 和 IEEE Communications Society ,現在 (14/6) 的費用是 USD $83.00。 付費後即可成為 IEEE 會員。

  6. 確認電郵中有連結住 myieee,那裡可以看到自己的編號和其他資料。
  7. 填寫及寄回 個人登記為功能界別選民及選舉委員會界別分組投票人申請書 REO-41

請緊記登記必須在2012年5月16日前完成,由於處理需時,請不要遲疑了!快點登記做選民吧!!

注意:以上內容雖然力求準確,但只供參考之用,請各位小心確認資料,一切後果本人概不負責。

UPDATE:

哇靠,原來當年劉夢熊選過IT界選委

參考資料

相關文章

Using 3rd Party Ruby Library in RubyMotion

| Comments

Update: Check the post Create gems for RubyMotion on example to create a gem for rubymotion.

These few days I have been playing with RubyMotion.

They have documents on using Objective-C libraries. So using familiar library such as TouchJSON is not an issue.

How about using Ruby library such as BubbleWrap? There are no documents on how to include and write gems in your project yet. Before it is ready, you can do this:

Add the library as git submodule:

1
git submodule add https://github.com/mattetti/BubbleWrap.git vendor/BubbleWrap

Add the path to the library sources to your project ‘Rakefile’

1
2
3
4
Motion::Project::App.setup do |app|
  app.name = 'myapp'
  app.files += Dir.glob(File.join(app.project_dir, 'vendor/BubbleWrap/lib/**/*.rb'))
end

Thats it!

有關 Bundler 的二三事

| Comments

今天聽 Ruby Rouges podcast #045, 學了一些之前不懂的 bundler 小知識.

Dependency management 問題是 NP-Complete

Bundler,或任何 dependency management 軟件的算法,也是 NP-Complete 的問題

Bundler 的工作其實是在所有 dependencies 的版本上搜尋能符合 Gemfile 內容的解。大部份情況 bundler 也可以很快找到答案,但有一些 Gemfile 會讓 bundler 停轉。雖然看上去像 bug,但實際上不是個 bundler 作者可以輕易解決的問題。

話雖如此,但其實有方法加快 bundler 的搜尋:在 Gemfile 的 dependencies 盡量指定你想要的版本。

例如這個 Gemfile 的 Gems 沒有指定版本:

gem 'rake'
gem 'rack'
gem 'jekyll'

Bundler 預設值下,gems 版本等於 ‘>= 0’ (每一個版本也可以)。假設每個 gems 有 10 個版本,(簡單起見不計他們的 dependencies)這個 Gemfile 的版本的 search space 便有 103 個。

但如果將之指定版本 :

 gem 'rake', '~> 1.3.0'
 gem 'rack', '>= 0.9.2'
 gem 'jekyll', '>= 0.11.2'

那他們的 search space 便會降到只有附合條件(數個到數十個)的版本了!

一般會讓 bundler 停轉的 Gemfile 在指定較少範圍的 gems version 後都能解決問題 。

semantic versioning 和 approximate operator

如果你在用 Bundler 那你大概己聽過 semantic versioning。上面 Gemfile 例子裡的 approximate operator (~>) 意思為 “只要版本大過或等於指定的版本,最後一個版本號碼可以不理”。例如 ‘~> 3.1.2’ 即 ‘>= 3.1.2 and < 3.2.0’,’~> 3.1’ 等於 ‘>= 3.1 and < 4.0’。這是基於 semantic versioning 的假設:如果在版本 a.b.c 測試過,,那麼在之後的 a.b.d 也應該可以運作正常 – 因為最後一個數字僅代表 patch / bug fix 版本。

值得留意的是,~> 這符號之前沒有名字。Rubygems 的 committer 有段時期將之稱為 “spermy operator”!這事還引發了一串討論,最後 ~> 官方名稱就確定爲 “approximate”。

Bundler 1.1.0 的改進

如果你有用過 bundler 也會覺得它初次 bundle install 時很慢。也許大家會覺得那是因為 dependency resolution 的問題。然而大多數時間這也不是 NP-Complete 問題的問題. bundler 一直慢的原因是因為在解決版本問題之前它先會由 rubygems 下載整個 rubygems 資料庫 – 由古到今所有的 gems 的不同版本 – 將之解壓和放到計憶體上,這不是個很快的過程。

在 Bundler 1.1.0,他們使用了 rubygems.org 的新功能:只返回需要 gems 的資料。這讓需要下載和處理的資料由 M 降到 K 為單位!如果你在用 Bundler,那你一定要看看你更新到最新版本了沒!

推介你聽聽 Ruby Rouges podcast #045 裡面有我未能盡錄的好內容啊!

Re: 邀記者實測:數碼通狂踩CSL LTE稱3G做得好才重要

| Comments

今天在傳媒看到這篇文章:邀記者實測:數碼通狂踩CSL LTE稱3G做得好才重要

看數碼通的說法還真是玄之又玄,但其實可以解讀為:

貶敵抬己,其法有三。

其一,敵先有 4G 者,先貶其覆蓋不足。

其二,敵覆蓋充足者,貶測試者擇地失當,氣候不佳。

其三,若敵方長勝,貶”若果一多人用,頻譜多不等於速度會快”!

… 這麼一來自家用戶就士氣一振了吧?