Koders.comにObjective-Cもあることに気づいたので、ちょっと検索してみたら、CURLProtocolというものを発見。 これはGNUstep WebKit(gswebkit)というプロジェクトの一部で、GNUstep用にWebKitを実装したものらしい。
シイラのSRHTTPURLProtocolと同じように、CURLを利用してNSURLProtocol(gswebkitではURLProtocol)のサブクラスを実装したもの。
URLProtocolクラスの実装も見ることができるので、仕様を確認したいときには参考になるかもしれない。
[ruby-talk:135308]で文字列の変換に関する問題点が報告された。 報告された現象は、RubyCocoaについているサンプルコードitunes_abums.rbを実行するとエラーになってしまうというもの。
現在、RubyCocoaではRubyとCocoaの文字列の変換に-[NSString cString]や-[NSString stringWithCString:length]を利用している。 これらのメソッドは文字列がデフォルトエンコーディングであることを前提として動作している。 デフォルトエンコーディングは、ユーザの言語環境(システム環境設定)に依存する。 例えば、日本語ならばCoreFoundationでいうところのkCFStringEncodingMacJapaneseになる。
デフォルトエンコーディングに変換できない文字があるとき、例外NSCharacterConversionExceptionが発生してしまう。 これが今回わかった問題だ。
じゃ、対策を考えてみよう。エラーが起こらないようにするには、
の2つが考えられる。で、考えた対処案は次のもの
$KCODEを使うメリットは、Rubyで文字列を処理する際に有利じゃないかと(ちゃんとは考えていない)。 デフォルトエンコーディングを使うメリットは、、、あまりないかも。 日本以外の非ASCIIなプログラムはどうかかれているのだろう?
今のところA案を押し。あとでMLになげるけど、意見・提案はWikiのRubyCocoaTodoでも受け付けます。
あと、Shift JISはkCFStringEncodingMacJapanese, kCFStringEncodingShiftJIS_X0213_00, kCFStringEncodingShiftJIS, kCFStringEncodingDOSJapaneseとかあるのが悩ましい。
先日のrubyも含めてRubyCocoa.frameworkにいれてしまおうという話も考えてみよう。
といったところだろうか。 $LOAD_PATHはrb_init_loadpath()での初期設定はやらないほうがよさそう。 この場合はアプリケーション外のライブラリを利用できても困るし。
本体のほうに$LOAD_PATH関連の修正を入れる必要があるけど、技術的にはそんなに問題なさそう。
前回は半年以上前。 基本的にはほとんど同じだけど、NSObjectのundefined系をオーバーライドするのでなく、RubyCocoaのOverrideMixin.mの中でオーバーライドを定義。 このほうがルートクラスいじるよりは心理的な負荷が小さいかな。
0.4.1へのパッチ。人柱な方はどうぞ。 いちおうByeByeSmokeのRubyCocoaBinding.[hm]やbinding.rbをプロジェクトから削除したものをビルドして、簡単な動作確認はしました。
Index: framework/src/objc/OverrideMixin.m
===================================================================
RCS file: /cvsroot/rubycocoa/src/framework/src/objc/OverrideMixin.m,v
retrieving revision 1.2
diff -u -r1.2 OverrideMixin.m
--- framework/src/objc/OverrideMixin.m 12 Dec 2002 07:05:15 -0000 1.2
+++ framework/src/objc/OverrideMixin.m 6 Apr 2005 13:00:30 -0000
@@ -208,6 +208,20 @@
return nil;
}
+static id imp_valueForUndefinedKey (id rcv, SEL method, NSString* key)
+{
+ id slave = get_slave(rcv);
+
+ return [slave valueForKey:key];
+}
+
+static void imp_setValue_forUndefinedKey (id rcv, SEL method, id value, NSString* key)
+{
+ id slave = get_slave(rcv);
+
+ [slave setValue:value forKey:key];
+}
+
/**
* class methods implementation
**/
@@ -282,6 +296,8 @@
"respondsToSelector:",
"methodSignatureForSelector:",
"forwardInvocation:",
+ "valueForUndefinedKey:",
+ "setValue:forUndefinedKey:",
};
static struct objc_method imp_methods[] = {
@@ -305,6 +321,14 @@
"v8@4:8@12",
(IMP)imp_forwardInvocation
},
+ { NULL,
+ "@12@0:4@8",
+ (IMP)imp_valueForUndefinedKey
+ },
+ { NULL,
+ "v16@0:4@8@12",
+ (IMP)imp_setValue_forUndefinedKey
+ },
};
Index: framework/src/ruby/osx/objc/oc_wrapper.rb
===================================================================
RCS file: /cvsroot/rubycocoa/src/framework/src/ruby/osx/objc/oc_wrapper.rb,v
retrieving revision 1.2
diff -u -r1.2 oc_wrapper.rb
--- framework/src/ruby/osx/objc/oc_wrapper.rb 12 Dec 2002 07:05:17 -0000 1.2
+++ framework/src/ruby/osx/objc/oc_wrapper.rb 6 Apr 2005 13:00:30 -0000
@@ -55,6 +55,24 @@
end
end
+ def valueForKey(key)
+ if m = kvc_accessor(key.to_s)
+ return send(m)
+ else
+ super
+ end
+ end
+
+ def setValue_forKey(value, key)
+ if m = kvc_setter(key.to_s)
+ willChangeValueForKey(key)
+ send(m, value)
+ didChangeValueForKey(key)
+ else
+ super
+ end
+ end
+
private
def analyze_missing(mname, args)
@@ -100,6 +118,19 @@
m_name =~ /^mutableCopy/)
end
+ def kvc_accessor(key)
+ [key, key + '?'].each do |m|
+ return m if respond_to? m
+ end
+ return nil # accessor not found
+ end
+
+ def kvc_setter(key)
+ [key + '='].each do |m|
+ return m if respond_to? m
+ end
+ return nil
+ end
end
end # module OSX"@12@0:4@8"のような、謎の文字列。型はADCの型エンコードに書いてあるからわかるけど、この数字なにさ?
構造体objc_methodの説明では、「数字は気にしないでね。MacOS Xでは無視してるし」とのことなので、全部ゼロとしてもよさそう。 でも、それも気持ち悪いので、NSObjectのを調べてコピペした。
#import <Foundation/Foundation.h>
#import <stdio.h>
#import <objc/objc.h>
#import <objc/objc-class.h>
void method_attr(SEL sel) {
Method method;
method = class_getInstanceMethod([NSObject class], sel);
if (method == NULL)
return;
printf("name: %s\n", (char *)method->method_name);
printf("type: %s\n", method->method_types);
}
int main(int argc, const char * argv[]) {
id pool = [[NSAutoreleasePool alloc] init];
method_attr(@selector(valueForUndefinedKey:));
method_attr(@selector(setValue:forUndefinedKey:));
[pool release];
return 0;
}出力結果
name: valueForUndefinedKey:
type: @12@0:4@8
name: setValue:forUndefinedKey:
type: v16@0:4@8@12selfのサイズってゼロなのか...?
各所で話題の高橋メソッド。 それっぽく見せるビューをRubyCocoaで書いてみた。
TMView#setText(*1)すると、渡した文字列がはいるように描画する。 そんだけ。
require 'osx/cocoa'
class TMView < OSX::NSView
def initialize
@style = OSX::NSParagraphStyle.defaultParagraphStyle.mutableCopy
@style.setAlignment(OSX::NSCenterTextAlignment)
@font = OSX::NSFont.fontWithName_size('HiraKakuPro-W6', 36.0)
end
def setText(str)
attrs = {OSX.NSFontAttributeName => @font}
base_size = str.sizeWithAttributes(attrs)
ratio_w = bounds.size.width / base_size.width
ratio_h = bounds.size.height / base_size.height
ratio = ratio_w <= ratio_h ? ratio_w : ratio_h
draw_h = base_size.height * ratio
draw_rect = [0, (bounds.height - draw_h) / 2,
bounds.size.width, draw_h]
font = OSX::NSFont.fontWithName_size(@font.fontName, @font.pointSize * ratio)
attrs = {OSX.NSFontAttributeName => font,
OSX.NSParagraphStyleAttributeName => @style}
lockFocus
OSX::NSColor.whiteColor.set
OSX.NSRectFill(bounds) # wipe
str.drawInRect(draw_rect, :withAttributes, attrs)
unlockFocus
end
endドロワーに書いた文字列を表示させるアプリをつくってみた。


と、文字が増えると小さくなる。 改行位置は元のテキストに従う。
ちょっと考えてみると、昨日の実装はいまいち。 setText内で描画するんじゃなくて、drawRectをオーバーライドしたほうがよかったな。 ビューのリサイズや印刷(するのか?)にも対応できるし。
既にJavaScriptで実現したもの(via NDO::Weblog)もあるけど、面白そうなのでRubyCocoaでのプレゼンテーションツールにチャレンジしてみよう。
先日のDougさんから「やったよ」とのメールがきた。 彼のblogのエントリにも書かれている。 (パッチへのリンクもあり)
ruby install.rb configにオプションを与えると、組み込み用のRubyCocoa.frameworkをつくるようになる。 注目するところとしては、dylibのときにも対応させようとしているところ。(たぶん未完成) 技術的には面白そうだけど、そこまでしなくてもなあと思う。
あと、ruby install.rb install時の動作をどうしたらいいかは、ちょっと考えてみたほうがよさそう。 /Library/Frameworksにインストールされても困るし、stripはインストール時にやるようになっているのでbuild/にあるものをそのままで使うのもなんだか。
英語のメールをこれから書くには眠いので、返事は明日。
[ruby-talk:137711]によるとtigerのrubyは1.8.2-releaseらしい。 今のタイミングからすればそうか。
ちなみに[ruby-dev:25998]でまつもとさんが今週1.8.3のpreviewをだすとのアナウンスをしていた。 RubyCocoaも確認しておかないとね。
mkinoさんが、シイラでつかっているローカライズの仕組みの公開予告をしていた。 裏側でnibtoolが動いているってのは以前に聞いたけど、実際どうなってるか楽しみ。
でも、それよりも名前のknyk(コニャック)が"翻訳コンニャク"からきたものかどうかが気になってしかたがない。
Dougさんのパッチをひととおり見た。
といった具合か。
install.rbにruby 1.6でビルドできなくなる修正があったので、ありがとうメールで指摘した。
rubycocoa-talkで「pure ruby(OSX::NSXxxxのサブクラスでない)オブジェクトを使ってたら期待したとおりにならない」という報告があったので、ちゃんとコードを読んでみた。 CocoaBindingsの再実装をやっていたときに「お?もしかして..」と思っていたけど、確かに使えるらしい。
Stringなどいくつかのクラスは変換されCocoaのオブジェクトになるのは知っていたけど。 なんでもCocoa側に渡せるとは思っていなかった。(*1)
メンテナなのにそんなことも知らないの?と言われたら「その通り」と答えるしかない。 自分がRubyCocoaのコードを書くときは、なんとなくOSX::NSObjectのサブクラスを定義していたのだけど、そんな必要はなかったんだ。
これってたくさんあるrubyのライブラリのパワーをRubyCocoaアプリケーションでもさくっと利用できるんじゃないの?と思うわけだ。(*2) なんかドキドキ。
もちろん、CocoaBindingsを利用したり、nibファイルで利用したりするにはNSObject(のサブクラス)であることをCocoaシステムは期待しているのでうまくはいかない。 ただし、Objective-C/Cocoaは多くのケースでDuck Typingにできているので、プロトコルを実装すればよい場合も多い。 Key Value CodingやKey Value Obeservationをrubyのモジュールとして定義できればCocoaBindingsはpure rubyなクラスでも利用できるようになると思う。
今までに考えたことをメモ。難しそうなものから。
[1] ruby世界ではCocoaのクラスがフラット
rubyからみると、CocoaのクラスOSX::NSXxxxはすべてOSX::ObjCIDのサブクラスでフラットになっている。 これを知らないでCocoaのプログラムをRubyCocoaに書き直して練習していたりすると、ときどき動かないので注意。 たとえば、OSX::NSStringに追加したメソッドはOSX::NSMutableStringでは使えないとかいうことが起きる。
最近はソースをけっこう読んでるんだけど、これをどうにかするのはけっこう難しいと思う。
[2] 定数?それとも関数?
OSX::NSOKButtonは定数で、OSX.NSAppやOSX.NSShiftJISStringEncodingはモジュール関数だ。 もちろん内部的な理由があってのことだし、ヘッダファイルを見ればどちらかはわかるのだけど、感覚的にはすべて定数なんじゃないかなあと。 これはどうにかできそうだと考えている。 ただ、既存のスクリプトと互換がなくなるかもしれないのでいまいち踏み切れない。
今のところはinclude OSXしてしまえば、考えなくてよくなる。(*1)
[3] クラスメソッドのオーバーライド
ns_overridesはインスタンスメソッドのみオーバーライドできる。 クラスメソッドはできない。
これは技術的にはさほど問題はないのだけど、インターフェイスをどうするかが課題。 ns_class_overridesとか?なんだかなあ...
rubycocoa-talkでrubyスレッドを動かした際の問題が報告された。 (とちゅうまで誤解してて自分はちがう話をしていたのだけど) ネイティブスレッドとrubyスレッドがぶつかって...という話。 現象としては理解できるのだけど、どこから手をつけたらいいのか見当もつかず。
ruby/tkやWindows系のGUIフレームワークのように(*1)、rubyは1つのネイティブスレッドでだけ動くようにするってのが解決策なのかなあ。 まだ全然調べてないけど。
ぼくの手には余りまくるので、rubycocoa-talkへの参戦を熱望します>ふじもとさん
ふじもとさんから返事があった。 ありがとうございます。
今のRubyCocoaの振るまいはけっこう気に入っていて、Cocoaのクラスがrubyのクラスにみえるとこなんか、はじめて見たときは衝撃を受けたものだ。 「なんでこんなことができるワケ?」と。 で、その衝撃とらくちん加減がRubyCocoaを好きになった要因だったんだと思う。 というわけでサービス精神に感謝っ。
ぼくの今のとこの理想は、Cocoaのプログラムをrubyに翻訳したらそのまま動く世界。 それで思っていたのが昨日のエントリになったというとこ。
とりあえず今はメンテナンスしつつ、アプリケーションを2つ3つくってみたいな。 まずは使わなくっちゃね。
といいつつ、思い出したので1つ追加。
[4]Cocoaクラスのサブクラスのサブクラス
Cocoaクラスのサブクラスはうまく動く。そのサブクラスを利用するとうまくいかないことあり。 OSX::NSDocumentのサブクラスのサブクラスを定義して、Info.plistでクラス記述すると落ちたりする。
かなり周回遅れだけど、読み飛ばしまくっているruby-talkでBlueClothという聞きなれない名前がでていたのでちょっと見てみた。 だいぶ前にtDiary界隈でMarkdownスタイルが話題になっていたときにMarkdownの名前自体はきいていたのだけど、そのときはスルーしてた。
この記述法の面白いところは、リンクをインラインでなく別のブロックで定義できるところ(インラインでもできる)。 これはいいかも。 RDのインラインはちょっとわずらわしいなと思っていたのだ。 いまだRD->htmlが主要なhtml作成手段なんだけど、ちょっと試してみよう。
ちなみによく使う記述形式はRD、BitChannel、nDiary、tDiaryオリジナルあたりなのだけど、気に入っているのはBitChannel形式。 シンプルなのと、"{{{" "}}}"で囲むだけで整形済みになるところがよい。 BitChannelのWiki上でしか使わないけど。
気になっていたLinkBackの記事がMacDevCenterに掲載された。 Cocoa勉強会で発表しようと思っていたのに先を越されたなあ。 まだドキュメント読んだくらいなので、来週には絶対間に合わないのだけど。
LinkBackはドキュメント中のオブジェクトを他アプリケ−ションから編集できるようにする仕組みで、Macに昔あった「発行と引用」やWindowsのOLEに似ていると言われているようだ。(どちらもよく知らない) オブジェクトを編集しようとすると、別のアプリケーションにも同じオブジェクトが表示され、それを編集すると元のアプリケーションに反映される。
ちょっとした作業を他アプリケーションにやってもらう仕組みとしては、サービスメニューがあったけど、ふつうに編集させるようなのとはちがっていた。 元アプリケーションが待ちに入っちゃうし。
まだ対応しているアプリケーションは少ないけれど、開発者だけでなくアプリケーションのユーザにもメリットがあると思うので、普及してもらいたい技術だ。
こないだ書いたのはまちがいだった。 include OSXしてもダメだった。
それだけだと後ろ向きなので、関数で提供されているものを定数のように見せかけられないかを試してみた。 次のようにモジュールOSXに特異メソッドを定義。
def OSX.const_missing(sym)
if OSX.respond_to? sym
OSX.send(sym)
else
Object.const_missing(sym)
end
endするとこうなる。
ハンパだ。 Object.const_missingは1.6以前では使えないし。
webkitsdk-devに投稿されていたWYSIWYG comes to Safari 1.3。 WebKitがSafari1.3でTiger相当になったということで、MacOS X 10.3.9では、WebView上でHTMLの編集ができるようになったようだ。
ただ、どれもこれもが編集できるわけではなくて、contentEditable="true"である要素だけが編集できるようだ。 ContentEditableWikiが動作するか試してみたけどダメだった。 「編集」をクリックしても、編集できるようにならない。 JavaScriptのほうが原因かなあ。
わりと素直な実装を思いついたので、おとといあたりから作業、検証してコミット。 #9をちょっとだけ直したもの。
rubyでCocoaなクラスのサブクラスを定義すると、自動的にKey-Value Codingが利用可能になる。
KVCの話つづき。 rubyプログラムでkey-valueにアクセスするには、Cocoaのオブジェクトと同様にobj.valueForKey(key)やobj.setValue_forKey(value, key)といった形式で利用する必要がある。
でも、これってわずらわしいよね。 というわけでMLで提案があった。 10日前のメールなんだけど、ちゃんと読んでなかった、ゴメンナサイ。
おおざっぱに説明すると、まずkvc_accessorというユーティリティメソッドを用意する。 これはattr_accessorみたいなもので、KVC用のアクセサを定義する。 これで定義したwriter(setter)メソッドではwillChangeValueForKey/didChangeValueForKeyが実行される。
こうすることで、key= varというようにruby上で記述するとsetValue_forKeyと同様の結果が得られる。
これってなかなかいいじゃん、と思うのだけどいくつか弱点が。 いちばん気になるのは、writerのメソッドを通常のメソッドと同じように定義する場合は、kvc_accessorを定義後に呼び出す必要がある。こんな感じ。
def dollarsToConvert=(doller)
@doller = doller
end
kvc_accessor :dollarsToConvertこれって直感的ではないなと思うわけ。 たぶんmethod_addedを利用して、writerがオーバーライドされたときに処理することで対応できるんじゃないかと考えている。 まだなにも試してないけどね。
バグ発見。1行の文字が長いときに、正しく表示されなくなる。 段落のスタイルにNSLineBreakByClippingを設定することで解決。
今のコード。もうちょっとしたら簡単なアプリケーションを公開できる予定。
require 'osx/cocoa'
module OSX
class NSSize
def *(ratio)
return OSX::NSSize.new(self.width * ratio, self.height * ratio)
end
end
end
class TMView < OSX::NSView
ns_overrides 'drawRect:'
BASE_FONT = OSX::NSFont.fontWithName_size('HiraKakuPro-W6', 36.0)
def initialize
@text = nil
end
# "str" must be NSString
def text=(str)
@text = str
setNeedsDisplay(true)
end
def drawRect(rect)
super_drawRect(rect)
return unless @text
wipe(OSX::NSColor.whiteColor)
base_size = unit_size(@text)
ratio = resize_ratio(base_size, bounds.size)
text_rect = text_rect(base_size * ratio, bounds.size)
@text.drawInRect(text_rect, :withAttributes, text_attrs(ratio))
end
private
def resize_ratio(base_size, view_size)
ratio_w = view_size.width / base_size.width
ratio_h = view_size.height / base_size.height
return [ratio_w, ratio_h].min
end
def text_rect(text_size, view_size)
return [0, (view_size.height - text_size.height) / 2,
view_size.width, text_size.height]
end
def unit_size(text)
return text.sizeWithAttributes(text_attrs)
end
def wipe(color)
color.set
OSX.NSRectFill(bounds)
end
def text_attrs(ratio = 1.0)
font = OSX::NSFont.fontWithName_size(
BASE_FONT.fontName, BASE_FONT.pointSize * ratio)
style = OSX::NSParagraphStyle.defaultParagraphStyle.mutableCopy
style.setAlignment(OSX::NSCenterTextAlignment)
style.setLineBreakMode(OSX::NSLineBreakByClipping)
return {OSX.NSFontAttributeName => font,
OSX.NSParagraphStyleAttributeName => style}
end
end日曜のCocoa勉強会なんだけど、財布を忘れてしまって会場費などをツケにしてもらった。 みなさんごめんなさい。
今回の発表でとくに面白かったのは、神谷さんのRD(HDDレコーダ)の話と成田さんのInterface Builderの拡張の話。
RDのはまだとっかかりみたいな感じだけど、予約をらくちんにするためのインターフェイスとかいろいろ考えることがあって楽しい。 新聞の番組表みたいなスタイルで表示させるとしたら、自分でビューつくらないとだめかな? チャンネルのビューをくっつけて強調してサイズが決まっていくようなのかな。 妄想はふくらむ。
IBの話は内容も興味深いけど、NeXTStep/OpenStepの資料がまだまだ使える、というかそれしか資料がないって状況が衝撃。 CocoaプログラミングのアドバンテージはInterface Builderの存在がかなり大きいと思ってるので、情報はいくらでもほしいな。
さらにつづき。 rubycocoa-talkにもポストしたけどModule#method_addedでどうにかするのを考えた。 方針は、
書いたらよけいわかんなくなってきた。コードみたほうが早いな。
module NSBehaviorAttachment
def kvc_accessor(*keys)
# define setter if needed
# alias setter to internal setter
# define wrapper
# alias wrapper to setter
end
def kvc_internal_setter(key)
# returns wrapped setter name
end
def kvc_setter_wrapper(key)
# returns wrapper name
end
def method_added(sym)
return unless sym.to_s =~ /([^=]+)=\z/
key = $1
setter = kvc_internal_setter(key)
wrapper = kvc_setter_wrapper(key)
return unless method_defined?(setter) && method_defined?(wrapper)
# do not re-alias wrapper
return if self.instance_method(wrapper) == self.instance_method(sym)
# re-alias wrapper to setter
alias_method sym, wrapper
end
endミソとしては、ラッパーのaliasを付けなおすときに注意しないと無限ループしてしまう。 この実装では、UnboundMethodを比較することでループしないようにしている。 正直なとこUnboundMethod#==でうまくいくか自信なかったのだけど、大丈夫だった。
高橋メソッドなRubyCocoaアプリケーションができたので公開。 テキストファイルを読み込んで、できるだけ大きく表示するだけ。
ブラウザでできるものなどがすでにあるのに意味があるのかっていうと、ちょっと疑問がなくもないけど、ドキュメントベースの勉強になった。
とくに問題なくコンパイルできる模様。 ただしXcodeToolsのインストール時にgcc3.3にチェックをつけること。
はじめにgcc4.0だけの環境をテストしようとしたら、gcc_selectすら使えなかった。
ちなみにruby-1.8.2のソースからのインストールに問題があるみたい。 なぜか拡張ライブラリreadlineを作ろうとしてエラーになってしまう。