Hatena::ブログ(Diary)

きりかノート 2冊め

2014-07-21

[] RubyCocoa 今日のコミット 2014-07-21 NSData.dataWithRubyString()をdeprecatedに

いちおRC出したのでそんなにいじるつもりはないので、yardのドキュメントをメンテしたり。

便利メソッドとしてOSX::NSData.dataWithRubyString(str)というメソッドが昔からあるんだけど、使い道があんまりないなーと思った上に、そもそもエンコーディングなしにバイト取るのもどうなんだろ?と思ったのでdeprecatedにした。NSMutableDataのメソッドも同様。

  • yardで@deprecatedを指定。NEWSにも書いた。実行時にはwarnする。(r2611)
  • テストで使ってるとこを必要な箇所を除きNSString#dataUsingEncoding()に直した。(r2612)
  • NSData#lengthのテストに非asciiなデータも足しておいた。(r2613)

たぶん誰も使ってないと思うので、1.2の次の次くらいのリリースで削除します。

2014-07-20

[] RubyCocoa 今週のコミット ..2014-07-20 サンプルアプリの動作確認

sample/以下にあるアプリの一覧をつくって、ひたすら確認して直してた。

  • もともとおかしかったぽい
    • make系のプロジェクトで-framework AppKitが足りない。
    • import <AppKit/NSApplication.h>足しておいた。
  • ruby-2.0によるもの
    • ruby-2.0でSyntax Errorになるものを直した。

他のアプリのプラグインとかは確認するのが大変なのでコンパイルできるとこまでしか確認していないです。

[] RubyCocoa 1.2.0 RC (Ruby-2.0対応版)を公開

しました。いきなりリリースしてもよかったんだけど、自分だけの確認では不安だったので。

動作確認用なのでOS X 10.9用のみです。なにかあればお知らせください。ヤバそうな問題が報告されなければ次の週末あたりにリリースします。

これまで試してきた印象では、RubyCocoaによる問題よりもrubyが1.8から2.0になった部分のほうが影響が大きいんじゃないかと思います。String#each → #each_lineとかそういうの。

2014-07-16

WebVTTファイルを利用して動画に字幕を付ける

小ネタ。自分用のメモです。

WWDCのtranscriptをテキスト化しているasciiwwdcリポジトリwwdc-session-transcriptsをながめてて、拡張子".vtt"てなんじゃろと思って調べてみたところ、WebVTTという規格のテキストであることがわかった。

テキストはこんな感じで、字幕のテキストの他に表示する時間や表示する形式が書かれている。シンプルな構造だね。

   WEBVTT
   X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000

   00:00:13.236 --> 00:00:14.526 A:middle
   &gt;&gt; My name is Olivier Bonnet.

   00:00:14.526 --> 00:00:18.266 A:middle
   I'm the engineering manager for
   the CloudKit on the client side.

動画と組み合わせて字幕を表示させるには、html中のtrack要素で書いてやればよい。

   <video controls autobuffer autoplay loop id="wwdcvideo" width="100%">
       <source src="./2014/408_sd_swift_playgrounds.mov" type="video/quicktime" />
       <track kind="captions" src="./2014/408.vtt" type="text/vtt" srclang="en"
              label="English Subtitles" default />
   </video>

ちなみに2014年のWWDCの動画には最初から字幕データが入っているので、こんなことしなくても字幕付く。てゆか二重に表示される。

いやさ、Safariでそのまま見てると最大化しないと字幕のオプションでてこないから、気付かなかったんですよ!

2014-07-14

[] RubyCocoa 今日のコミット 2014-07-14 インストーラ作成スクリプトの調整

10.9 Mervericks用はruby-1.8とruby-2.0の両方がOSに付いてるので、それぞれ用のRubyCocoa.frameworkのインストーラをリリースする予定。なのでそのあたりの調整あれこれ。

今は自動的に"RubyCocoa-1.1.0-OSX10.9.dmg"などの名前を生成しているけれど、任意の名前も指定できるように。単にRubyのバージョンを足して、

  • RubyCocoa-1.2.0-OSX10.9-Ruby1.8.dmg
  • RubyCocoa-1.2.0-OSX10.9-Ruby2.0.dmg

になる予定。

パッケージ作成時の設定ファイルをpackage/config/以下におくようにして、`ruby install.rb package`で該当OSバージョンのファイルを使うんだけど、OSのバージョンが同じなので実行時のコマンドラインオプションで選べるようにした。

   % ls -1 package/config/
   10.4
   10.5
   10.6
   10.7
   10.8
   10.9
   10.9-ruby2.0 # <= NEW!

エンコーディングの件はまだちょっと悩み中。現在の案だとどうせ直すことになりそうだし、UTF-8使えれば現在の1.8レベルの利用は問題ないはずなので、次の次のバージョン(1.3?)に送っちゃおうかなあ、、とひよっている。今週末までの進行具合で判断しますです。

2014-07-10

[] RubyCocoa 今日のコミット 2014-07-10 ruby-2.xでThreadがおかしい?件に対応

だいたいruby-2.x対応できたので、samples/以下にあるアプリを動かしたりして動作確認してる。で、どうもrubyのThreadを使ったコードがうまく動かなくって、スレッドが切り替わらなかったりするのを調べてたんだけれどようやくわかった。

  • Thread.passがひつよう。(r2584)

RubyCocoa本体側を疑ってコード追っかけてたからぜんぜん気付かなかったぜい。


1.2.0リリースまでの残作業

今回は一度RC出す予定です。

2014-07-09

[]port:pecoを0.2.0に更新

しました。

先日MacPorts公式にport:pecoを登録してから気付いたんだけど、peco-0.1.12とMacPortsのportコマンドとの組み合わせで、どうも動作がおかしい。portの出力をpecoに渡すとそのまま固まってしまう(killしないと終了できない)。0.1.11以前は問題なかった。

pecoの変更を追っかけてみると、0.1.11と0.1.12で入力バッファの処理が変更されていて、どうもそのへんじゃないかと思ってレポートしたりして、報告した問題は解消されたんだけど、portだけまだおかしい。これはたぶんportが悪いんだと思う。

仕方ないので、パイプでつながないようにすることにした>"pbcopy; pbpaste"のあたり。

   % sudo port selfupdate
   % sudo port upgrade $(port outdated | pbcopy; pbpaste | peco | cut -f1 -d\ )

これで更新したいのを選ぶとupgradeされる。地味に便利。

2014-07-04

[] RubyCocoa 今週のコミット ..2014-07-03 standaloneify.rbのruby-2.x対応

1週間以上前にテスト全部とおるようになったんだけど、実際にアプリ試してみるとダメなところあったりで対処中。今はstandaloneify.rbを対応してるとこ。

  • ruby-2.xで"$0 not initialized"を修正。(r2581)
  • ruby-2.xでクラッシュするのを修正。(r2582)

簡単に説明。

rubyで実行中に$0の値を設定すると、プロセスの表示名(なんていうんだ?)を変更することができる。これを利用してstandaloneify.rbを実行中にターゲットのアプリのパスを$0に設定して、NSBundle.mainBundleがアプリになるようにしてたりする。これがruby-2.x上では"$0 not initialized"とエラーになってしまうようになった。

これはruby-2.0以降では実行しているプロセスがrubyコマンドのとき(正確にはruby_sysinit()されたとき)のみ、$0への代入を許可しているため。対応としては

  • $0へ代入しない。
  • ruby_sysinit()で初期化するようにする。

あたりなんだけど、とりあえず前者から試してみて対応できたのでそのようにした。CocoaではNSProcessInfoでrubyの$0と同様に実行中のプロセスの$0を変更できるのでそれを利用した。

次にクラッシュの件。standaloneify時のアプリで文字列Stringのインスタンスを生成すると落ちるというもので、これは数日調べてもぜんぜんわからなかった。なんだけど、ruby_run_node()での実行が終わったあとに操作してるのが問題ぽくて、そもそもstandaloneify時には必要ない処理だったのでクラッシュする箇所に到達する前にexit()するようにしたら直った。気付けば簡単なことなんだけど、これは苦戦した。。

あと残ってるのはどうもThreadの動作があやしくて、サンプルのSimpleAppからしてスレッド使ったとこがちゃんと動かない。たぶんこれ直したらリリースできると思う。

[] pecoのPortfileを本家に投入した

先日の”pecoのPortfileを書いてみた”から、納得いくカタチまで改善できたので、本家にコミットした。

   % port info peco
   peco @0.1.12 (sysutils)

   Description:          peco can be a great tool to filter stuff like logs,
                         process stats, find files, because unlike grep,
                         you can type as you think and look through the
                         current results.
   Homepage:             http://peco.github.io

   Build Dependencies:   go
   Platforms:            darwin
   License:              MIT
   Maintainers:          kimuraw@..., openmaintainer@...

これでMacPortsユーザも`port install peco`だけで使えるようになります。buildbotも正常に終了しているので、ふつーはport:goのインストールは不要です。

改善したところ。

  • 依存コードのgo-flagsなどもバージョン/リビジョンを指定して、tarballを取ってくるように。
  • peco本体と依存コードのブロックを分けるようにした。
  • checksumsの書式をきれいに。
  • コメントで補足書いた。
    • 依存コードのバージョン上げたらportのrevisionも上げてね。
    • port extract後のファイル構成。work/以下にgopathをつくって集める。

だいたいこのあたりはghqのPortfileも書きながら整理してたとこ。ghqのほうは、依存してるgoauth2google code上にあるMercurialリポジトリで、tarballとってくる方法がわからなくて保留中。別にhgコマンドで取ってくるのでもかまわないんだけど、ミラーの関係などでMacPorts的にはできればtarballやzipにしたいところ。

(2014-07-05追記:ソースツリーながめてたらふつーに"Download"のリンクがあった…。見落としてたよ、すまぬ。>google code)

github上にgoauth2のミラーがあればそっち使うんだけど、いくつか見た感じではforkで手が入ってるみたいなのでそれは使えないなあと。

ふだんだったら自分用につくって終わりなんだけれど今回本家にまで入れたのは、このあたりのツールは常識レベルになりそうな気配だったので可能ならMacPortsでも提供したいと思ったから。今どきはhomebrew使う人が多いだろうけれど、選択肢はあって悪いものじゃないしね。

2014-06-22

[][]第66回 Cocoa勉強会に行ってきた(6/14)

会場はいつもの新宿伊藤ビルの貸し会議室

以下発表など。内容的なことは勉強会のサイトのほうにレポートを書いたので、こちらは感想とか主観メインで。


発表「LightBlue Bean」(gadget)

BLE(Bluetooth Low Energy)で接続できる、いろんなセンサーついてる、Arduinoなどのフリスクサイズのデバイス。きょうびこの手のってすげえ小さくなってるよねえ。

当初の予定よりすごい遅れて勉強会の当日にSDKが公開されたんだけど、Objective-CAPIになってちょっとおどろき。Cの関数だけ並んでると思ってたよ。

あとで開発会社のWebサイト見たら、開発かかわってたらしいpopSLATEのAPIのとこも"Coming soon!"て書かれててちょっと笑ってしまった。


発表「iOSアプリ署名」(iOS)

アプリのビルドと署名の分割について。去年の12月の続き。

iOSアプリぜんぜんやってないから理屈で考えつつ聞いてた。ふだんXcodeいっぱつでできるのは簡単で良いけど、逆に隠蔽されてて融通きかないところもあるよね。


発表「OS Xの画面開発」(Mac)

storyboardの話。既存のアプリでViewControllerに手を入れまくってるから、いまさらレスポンダーチェーンに入れられても困る、という参加者の意見にもなるほど。まあでも新規アプリでしか使わんよね。


発表「irMagician」(gadget)

MacやPCなど別機器からの指示を受けてIRリモコンになるirMagicianの話。

発表者の他のユーザからオプションパーツのリクエストが出たりと、「開発してるなあ」と本質的でないとこで感心してしまったり。


発表「Swift」(swift)

Appleの新言語、swiftについて。発表者が自分用にplaygroundで試しつつ作ったという、大ボリュームのスライドを見ながらみんなであれこれ。

C++からObjcive-Cに来た人と、Rubyから来た自分でけっこう感じ方がちがうなあと思った。自分としては「実行速度を言語でとりにきたか」というのが第一印象。WWDCのちょっと前にrustについてちょっと調べてたのもあるけれど。


次回の予定

8/2に開催予定です。

[] RubyCocoa 文字列エンコーディング (続き)

先週の続き。

  • if/switchによる変換を、st_tableの変換表に。→実装した。
  • 名前によるエンコーディング変換表の自動作成。→うまくいかなかった。
  • 変換できないエンコーディング。→可能ならUTF-8に。

試しながら考えたや課題など。


st_tableによる変換表

Rubyのencoding indexとCFStringEncodingはどちもintなので特に問題なく。

これは「同じエンコーディングは同じ符号化(バイト列)」であることを前提にしてるので、RubyのUTF-8-MacをUTF-8にしてからCFStringに渡す、ということはできない。これを実現したい場合、入力エンコーディングに対して

  • 変換の要否、その方法
  • 変換先のエンコーディング

といった情報をHashなどで持たせるような変換表にする必要があるだろう。このようにするべきかどうかは結論がでてない。

また、Rubyのエンコーディングは拡張ライブラリ(?)になっていて必要なときに読み込みするようになっているので、最低限の変換表(ASCII-8BIT, UTF-8)だけ初期登録しておいて、あとは実行時に追加していくのが良いと思う。(そうしないとindexがわかっていても、エンコーディング本体がロードされてなくて妙な動作をしたりする。)ただこの「実行時に追加」をどうするのかが考えどころ。

名前(char *)の変換表を内部的に持つあたりかなあ。ということで次へ。


名前によるエンコーディング変換

RubyもCFStringも「エンコーディングの名前」を持っているので、これを使えばよいのでは?と試してみた。

CFStringEncodingで使えそうなのは、

  • IANA charset名 (CFStringConvertEncodingToIANACharSetName)
  • Windowsコードページ (CFStringConvertEncodingToWindowsCodepage)

あたり。IANAでない名前を取ることはできる(CFStringGetNameOfEncoding)けれど、名前から引くことはできないので除外。

RubyのEncodingで使えそうなのは

  • 名前 (Encoding#name)

あたり。RubyのEncodingは名前を複数持てるんだけど、Rubyのメソッドを通さないと拡張ライブラリからはひとつめ名称にしかアクセスできないっぽい。

で、とりあえずIANA charset名と名前で引けたものは自動的に変換テーブルに登録していくってのをやってみたんだけどうまくいかない。UTF-16がどうもうまくない。

NSString|CFStringは、Rubyの文字列とちがってある文字列を特定のエンコーディングとして認識しているのではなく、Cのバイト列との相互変換のときにエンコーディングを都度指定するようになっている。NSStringのインスタンスに対しては

  • fastestEncoding: バイト列にするときに時間がかからないもの。
  • smallestEncoding: バイト列にしたときそのサイズが小さいもの。
  • canBeConvertedToEncoding: 情報のロスなしに指定のエンコーディングに変換可能か。

などを問い合わせることができる。

この「エンコーディングそのものを持ってない。目的に応じて使えるエンコーディングを問い合わせることができる」てのがくせ者で、UTF-8で生成した文字列が、fastestEncodingでは(BEでもLEでもない)UTF-16を返すということがある。するとRuby側からUTF-8で渡した文字列がObjCから帰ってくるときにUTF-16になってしまう。これはNSString|CFStringの仕様的にどうにもならんのだけど気持ち悪い。

きょうびUTF-8がほとんどだと思うので、CFString(UTF-16)→Ruby Stringの場合はUTF-8に変換しちゃうほうがよいかもしれない。


変換できないエンコーディング

とりあえずUTF-8試してだめならバイト列という対応。internalやexternalというものがRubyにはあるので、それ使うほうがいいんじゃ?とも思ったけど、そのエンコーディングが変換可能なものかは実行時にならないとわからないのであまり良くないかなあと。

だいぶ(元の意味でも誤用の意味でも)煮詰まってきたので、ちょっと置いて今週は別の作業するつもり。

2014-06-21

[] pecoのPortfileを書いてみた

さいきんちらほら見かけた、絞り込み選択インターフェイスのコマンドpecoが気になったので試してみた。golangで書かれているので、goでビルドしてやる必要がある。

自分はMacPortsユーザなので、こういう状況になると「とりあえずPortfile書くか」というのがアプローチになるんだけれど、goの作法とかは全然わからない。ありがたいことに本家でhomebrew用のformulaが公開されてるので、それを参考にすればよい。

これでport install pecoで使える(注:本家にはコミットしていません)。標準出力が使えるってのが思ってた以上に強力で、どこでも差し込めるからあれこれ試しててけっこう楽しい。

で、Portfile書きながら、MacPortsとgolang製ツールの相性とかをぼんやりと考えていた。


○良いところ

buildbotが各OSバージョン用のバイナリ作ってくれるので、port:go入れなくてもインストールできる。今回なら${prefix}/bin/pecoだけ取ってくることになる。

また、goのことを知らなくてもインストールできる。ただこれは他のパッケージ管理ツールでも同様。

ちなみにpecoのissueながめてたところ、issue83に「drone.io使えばバイナリ自動ビルドできるよ」的なことが書いてあるので、そもそもそれ使えばよい話かも。


×良くなさそうなところ

今回は`go get`で逃げてるけれども、MacPorts的にはバージョンやタグを指定して、ライブラリ特定のリビジョンをdistfileとして取ってくるようにしたいところ。チェックサムの検証もできるし。

MacPorts自体はdistfilesにtagをつけることで、各distnameをそれぞれのmaster_siteから取ってくる機能があるので実現可能だと思う。

ただgithubのdownloadsがくれる.tar.gzなどはディレクトリにコミットidが入ってたりと使いづらい。それをPortGroup githubが解消してくれるんだけど、複数ファイルには対応してないっぽい。けっこう手を入れないと対応は難しそう。

Portfile書くにあたって、port:goが依存関係に入ってる公式portを検索してみたら、今朝時点ではport:codesearchひとつしかなかった。。

とくに結論とかはありません。

2014-06-17

[] standaloneify.rb トラブルシューティング

RubyCocoaに添付されてる便利スクリプト、standaloneify.rbで問題が起きたときの対処法。


standaloneify.rbとは

各種ライブラリ(.rb, .bundleなど)を.app内にパッケージして配布しやすくするツール。いつのころからかRubyCocoaに添付されてる。

わりと裏技的な実現方法を取ってるので、将来的にはGemfile使うとか別の手法に移行したい。


さいきんの変更

今日時点の最新版はSVNリポジトリの該当ファイルの"Download this file"あたりからどうぞ。

  • rubygems-2.1以降でも動作するようにした。
  • 実行時に情報をいっぱい出すようにした。

RubyCocoaやRubyのバージョンを出すようにした。ここで表示するのはstandaloneify.rbを実行したものでなく、アプリケーションが利用している(RubyCocoaがリンクしている)Rubyのバージョンなので、1.8系が出力されるはず。

   % ruby standaloneify.rb -f -d SimpleApp.app ./build/Default/SimpleApp.app
   INFO: RubyCocoa.framework (version 1.1.0) copied from "/Library/Frameworks/RubyCocoa.framework".
   standaloneifying Ruby Version: 1.8.7
   standaloneifying RubyCocoa Version: 1.1.0
   standaloneifying RubyCocoa path: /path_to/SimpleApp.app/Contents/Frameworks/RubyCocoa.framework
   standaloneifying Gem::VERSION: 1.8.26
   Found gem sqlite3
   Skipping RubyCocoa file "osx/objc/oc_exception.rb"
     :
   Finish: "SimpleApp.app" generated.

失敗したとき。

   % ruby standaloneify.rb -f -d SimpleApp.app ./build/Default/SimpleApp.app
   INFO: RubyCocoa.framework (version 1.1.0) copied from "/Library/Frameworks/RubyCocoa.framework".
   standaloneifying Ruby Version: 1.8.7
   standaloneifying RubyCocoa Version: 1.1.0
     :
   /path_to/SimpleApp.app/Contents/Resources/rb_main.rb:10: uninitialized constant FAIL (NameError)
       from standaloneify.rb:153:in `require'
       from standaloneify.rb:153
   >> command: /path_to/SimpleApp.app/Contents/MacOS/SimpleApp --rubycocoa-ruby-opt standaloneify.rb --standaloneify
   >> dyld: loaded: /fullpath_to/SimpleApp.app/Contents/MacOS/SimpleApp
   >> dyld: loaded: /fullpath_to/SimpleApp.app/Contents/MacOS/../Frameworks/RubyCocoa.framework/Versions/A/RubyCocoa
   >> dyld: loaded: /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/libruby.1.dylib
   Error: Couldn't read dependency list

実行したコマンドと、アプリの起動時にロードした"ruby"という文字を含むライブラリを出力する。これは、アプリ内にコピーしても/System/Library/FrameworksのRubyCocoa.frameworkを使ってないかなどを確認するためだ。


失敗したときに確認すること


アプリ内のRubyCocoa.frameworkは意図したバージョンになっているか。

XcodeビルドしたときにRubyCocoa.frameworkをアプリ内にコピー済みだとstandaloneify.rbは上書きしない。実行時の先頭の"INFO:"の内容で次のことを確認できる。

  • コピーした。その場合のコピー元。
  • すでにあったのでコピーしなかった。
  • アプリ内のRubyCocoa.frameworkのバージョン。

"standaloneifying Ruby Version:"等が出力されているか。

このメッセージが表示されているときは、standaloneifyモードでアプリが起動されているので、そこは問題ないということがわかる。その場合はなんらかのrubyのエラーが出てるので、そこを中心に調べる。だいたいは、standaloneify.rbが利用しているrubygemsのバージョンに対応してないことが原因。

逆に表示されていないときは、アプリがstandaloneifyモードで起動できていないので、

  • アプリのRubyCocoaとstandaloneifyスクリプトの不整合。
  • RubyCocoa本体のバグ

あたりが疑わしい。

今回情報をいっぱい出すようにしたので、最新のstandaloneify.rbを使った上で失敗したときの出力メッセージをまるごと私あてにお知らせください。調べます。

2014-06-14

[] RubyCocoa 今週のコミット ..2014-06-14 ruby-2.x対応

だいたいこんな感じ。

  • ruby-2.1上のテストの失敗をつぶす。
  • Ruby StringとNSString間のエンコーディング変換(超暫定)。
  • OS X 10.5以前用のコードを削除。
  • APIドキュメントの更新。
  • 開発用のinstall.rbやRakefileの整備。

テストの失敗は(狭義では?)RubyCocoaの機能の問題じゃなくって、1.8と2.1で動作が異なるところがだいたい原因だった。RubyCocoaではNSArrayやNSStringがRubyのArrayやStringと同じように使えるようメソッドをいっぱい生やしてる(osx/ruby/oc_attachments.rb)んだけど、そこらではまったと。

今週対処したところを挙げると、

  • String#each -> #each_line
  • String#index(int)=val, String#rindex(int)=val: 2.0では数(0..255でのコード指定)は受け付けない。
  • String#[idx]: 1.8では指定位置の文字のコード値の整数を、2.0では文字を返す。
  • String#include?(int): 1.8では数値が与えられるとそのコードの文字があるかを返す、2.0ではTypeError。
  • String#[self.length]=str: 1.8ではIndexError、2.0では末尾に文字列追加。
  • Range#each: 1.8では#succで終了値までまわす、2.0ではRangeの要素が:to_strを持っているときString#succで処理する。なのでmapするとStringの配列になる。
  • Array#[range] = nil: 1.8では指定範囲位置の要素を削除、2.0では指定範囲位置をnilに置き換え。NSArrayはnilを保持できないのでArgumentErrorに。(これ書きながら思ったけどTypeErrorじゃね?)

といったところ。

エンコーディング変換の件はちょっと長くなるので別エントリに。

ということで`rake test`の結果がr2556時点で

  • ruby-1.8.7: 0F0E
  • ruby-2.0.0: 1F1E
  • ruby-2.1.2: 1F1E

までになった。だいぶ進んだ気はする。

[] RubyCocoa 今週のコミット 2014-06-13 文字列エンコーディング

Ruby 1.8と1.9以降のちがいについては、るびま25号の”Rubyist Magazine - Ruby M17N の設計と実装”に詳しい。すげー雑に書くと、

  • ruby-1.8: Stringはバイト列。文字列処理や正規表現のマッチは$KCODEで制御。
  • ruby-1.9: 個々のStringインスタンスが自分のエンコーディングを知っている。

という感じ。CocoaのNSStringは後者と同様にエンコーディングを持ってる。

なので、RubyとObjective-Cで文字列オブジェクトを変換するときには、それぞれのエンコーディングに読み替えて処理するようにしてやればよい。中間データとしては例によって(char *)になる。

RubyのエンコーディングはEncodingクラスおよびそのインスタンスで表現され(内部的にはrb_encoding*とindexも使える)、NSStringではNSStringEncoding(CoreFoundationではCFStringEncoding)を使う。

それぞれ文字列の生成や、バイト列を取るときにエンコーディングを指定できるので、プログラム的には問題なさそう。

実装に当たって決めなきゃいけないことは、

  • RubyとNSString(CFString)のエンコーディングの対応関係のリストアップ。
  • RubyではCocoaならNSDataで扱うような文字でないデータもStringで扱うがそれをどうするか。
  • 相互変換できないエンコーディングがあったらどうするか。

あたり。

1つめは基本がんばるしかない。やってみないとわからないけど、IANA Character Setsの名前でマッチングさせて変換表を自動的に作れたりするとだいぶ楽になるかもしれない。

2つめはRubyではダミーエンコーディング、NSStringではCoreFoundationになるけどkCFStringEncodingInvalidIdとよくわからないデータで構成される文字列(?)を表現するエンコーディングがある(正しくはRubyのはそうではない)。Rubyにはバイナリとかバイト列用のダミーエンコーディングはあらかじめ定義はされていないので、rb_define_dummy_encoding()を使って自分で使うダミーエンコーディングを定義してやる必要がある。

3つめは次のどちらかかなあ。

  • 変換できそうな一般的なエンコーディング(たとえばUTF-8)に変換を試みる。できなければバイト列と同様に扱う。
  • バイト列として扱う。

今回の仮実装では前者とした。

で、これらをとりあえず実装してみたのがr2551になる。

恐ろしいことにこれでもけっこうちゃんと動いたりするわけだけど、もうちょっとマシにするためのタスクはこのくらい。

  • 変換をswitch/ifでなく、対応関係を登録したst_tableを使うようにする。Rubyはindexを、CocoaはCFStringEncodingを登録するようにするつもり。
    • ruby側からの変換表へのアクセス機能を提供。実行時に変換表を登録、削除、lookupできるように。
  • ダミーエンコーディングの定義をRubyCocoa初期化のときに行うように。
  • ダミーエンコーディングのStringをつくる関数を定義して、それを使うようにする。

だいぶゴールが近づいてきた感じがする。

2014-06-08

[] RubyCocoa 今日のコミット 2014-06-07 擬似オープンクラスの2.x対応

  • OSX.objc_classnames()を追加。 (r2530)
  • OSX.ns_import_all()を追加。require_framework時に呼ぶように。 (r2531)
  • テストコードの一部にns_importを追加。 (r2532)

ということで、やっとここまできたぜ。。

   % ruby2.1 install.rb test
      :
   Finished in 2.606181 seconds.

   417 tests, 2750 assertions, 12 failures, 5 errors, 0 pendings, 0 omissions, 0 notifications
   95.9233% passed

まだ未対応の文字列まわりを除けば、

   325 tests, 1994 assertions, 1 failures, 1 errors, 0 pendings, 0 omissions, 0 notifications
   99.3846% passed

と1F1Eまできた。ruby-2.0でも同様。


作業内容

RubyCocoaではObjctive-Cの同名のクラスがあると、class構文でRubyのクラスを定義したときにそのクラスをObjctive-Cのクラスと自動的にさし替えてしまう機能(さいきん擬似オープンクラスと呼ぶことにした)がある。詳しくは2月の記事見てください。

で、これがけっこうむちゃな手法で実現されててruby-2.0以降では使えない。で、対処として、

  • OSX.require_frameworkして、.frameworkをロードした後に
  • Objctive-CのクラスをぜんぶモジュールOSX以下のクラスとして定義する

という方法をとることにした。ずいぶんおおざっぱなやり方だけど、対象とするクラスの絞り込み条件などうまく思いつかなかったので、現状は全クラスが対象。そのうち見直すかも。

そのために新しいモジュール関数として

  • Objective-C全クラスを取ってくる機能としてOSX.objc_classnames()を
  • それらをクラス化するためにOSX.ns_import_all()を

導入した。全クラスといっても、Cocoa系でない"Object"などのObjective-Cクラスは対象から除いてるし、クラス化も名前が大文字ではじまるもののみにして、"_"ではじまるクラスなんかは除くようにしている。

OSX.objc_classnames()は1.8上であからさまに遅かったりするので、ns_import_all()もRubyじゃなくてObjective-Cで書き直すかもしれない。目的からしたらループまわすのは1回でよいわけだし。

require_frameworkでフレームワークをロードした場合はクラスを自動的にもってくるようにしたけれど、requireで.bundleを読んだときはそういうことはしていないのでOSX.ns_importが必要。テストコード上ではns_importを追加して対処。


残ってるテスト失敗


E - tests/tc_nsarray.rb:test_grep

Array#grepがNSNumber#to_strを呼び出そうとしてメソッドがないのでエラーになる。Arrayの中身をNSStringとかにしてもおきる。NSNumber#responds_to? :to_str はfalseなのでRegexp#===の中では呼ばれないと思うんだけどなあ。check_funcall_respond_to()がTRUEってことなんかね。よくわらん。


F - tests/tc_bs.rb:test_framework_loaded

明示的に読み込みしてないはずのAdreeBook.frameworkが読み込まれてる。たぶん上記のns_import_allの副作用

これはもうちょっと調べたら解決しそうな気がする。

(6/14追記: 解決した)テストで使ってるObjective-C用のコード入れてるobjc_test.bundleがAddressBook.frameworkにリンクしていた。おそらくフレームワーク内のクラスがns_import_all -> OSX.ns_import AB... -> NSClassFromString()されてフレームワークがロードされるのだろう。テストで使ってないWebKitがロードされてないよね?というテストに書き換えて解決。

2014-06-07

[] RubyCocoa 今日のコミット 2014-06-07 テストコードのruby-2.1対応

ruby-1.8と2.1の非互換でテスト失敗するところの対応あれこれ。

ruby-1.8ではrangeの範囲の値削除だったけど、1.9以降ではその位置にnilが入る。doc/ChangeLog-1.9.3に

   array.c (rb_ary_update): a[n,m]=nil no longer works as element deletion.

とあるとおり、動作が変わったようです。

RubyCocoaでは、osx/objc/oc_attachments.rbでNSArrayがArrayと同じように、NSStringがStringと同じように動作するようになどCocoa側のクラスにメソッド追加したりしてる。

で、今回問題になったのは、NSArray#[range] = nilとArray#[range]= nilが同じ動作になってるよね、ってテストなんだけど、対処としては単純にruby-1.9以降ではnilを与えるテストをスキップするようにした。NSArrayは要素にnil入れられないしね。

しかしこの互換機能はのちのちハマりそうだなあ。どっかで最新のRubyの動作にまとめて合わせてやる必要がありそうだね。ちょっと気が重い…

ruby-1.9以降ではInteger(nil)はArgumentErrorになる。(1.8では0を返す)

rubyの値をObjective-Cに持ってくるとこ変換のテストで、nilをCのintに変換する検証があったので、ruby-2.xではそのassertを実行しないようにした。

ruby-1.8では位置iのバイトの値(整数)だけど、1.9以降はi文字めの1文字を返す。バイトの値を取り出すところだったのでString#bytesを使うように書き換え。

これはちょっとちがう話。RubyのオブジェクトをObjective-C側に渡したとき、もともとCocoaなオブジェクトならそれを、StringやArrayなんかはNSStringやNSArrayに変換されるようになっている。そういったものでないRubyのオブジェクトはObjective-CのRBObjectというラッパクラスでくるんで渡すようにしている。

そのラッパオブジェクトと元のRubyオブジェクトの関連はキャッシュとして保持してるけれど、その動作のテスト。

     def test_rbobject
       test = OSX::TestRBObject.alloc.init
       o = SimpleClass.new
       n = test.addressOfObject(o)
       GC.start # add
       assert(n != test.addressOfObject(o)) # 1
       a = OSX::NSMutableArray.array
       a << o
       n = test.addressOfObject(o)
       GC.start # add
       assert(n == test.addressOfObject(o)) # 2
     end

1つめのassertでは新しくRBObjectのインスタンスが生成されるからアドレスがちがって、2つめのassertではaの要素として保持されるからキャッシュが残るという確認。で、ruby-2.1上では1つめのassert()が失敗する(アドレスが一致する)ようになっていた。キャッシュを掃除するためにGC.startを追加したのが今日の修正。そもそも1.8でもたまたま動いてただけで、テストの意図としては元からGC.start必要だったんじゃないかなあ。

2014-06-06

[] RubyCocoa 今日のコミット 2014-06-06 `?a`リテラル修正

ruby-2.x対応に向けて、2月にメモった課題を調べてる。

今日はテスト流すと何ヶ所かで"ArgumentError: invalid value for Integer()"って例外がでて失敗するとこがあるのについて。エラーが出てるのはrb_Integer(str)で文字を整数値に変換しようとしているところで、これは動作としては正しい。

`?a`というリテラルの意味がruby-1.8と1.9以降で異なるのだ。 ruby-1.8では(1.8のリファレンス)

?a

文字 a のコード (97)

と数値をあらわすのに対し、1.9以降(2.1のリファレンス)では

?a

文字 a を表す String

と、文字になっている。

たとえばテストtc_types.rbでは次のようなテストがあるが、

     def test_char_conversion
       v = OSX::NSNumber.numberWithChar(?v)
       assert_equal(?v, v.charValue)
     end

これは`?v`の表す意味が1.8では118という数値なのに対し、2.1では"v"という文字になっているのだ。そりゃ失敗するよな。

ということで数字で書くように直した(r2524)

2014-06-05

[] RubyCocoa 今日のコミット 2014-06-05

次のOS Xのバージョンは10.10に決まりましたね。そうならないことを期待していたのに。。

ビルドスクリプトの中で、"バージョン文字列".to_f >= 10.8とかやってるとこがいっぱいあったので直した。ついでに10.5以前用の処理がいくつか残ってたので、そいつらは削除しといた。(最新のRubyCocoaは10.6以降に対応)

   "10.9".to_f >= 10.7  # true
   "10.10".to_f >= 10.7 # false, 10.1 < 10.7

先日Ruby本体のfeature requestで、「Feature #9816: 文字列内の数字を数値として比較するメソッド - ruby-trunk - Ruby Issue Tracking System」てのがあったけれど、これが入ってれば楽に比較できたんだけどねえ。

とりあえず今回は場所によって次の作戦をとることにした。

  • がんばって比較する。
  • 2つ目の値(10.9なら9)のみを比較する。

後者は"11.x"とかになったらまたハマるけどまあいいや。