Cocoa

NSURLのエスケープ指定

in

clippyのエイリアスに日本語ファイル名を指定すると読み込めないというバグがありました。自分では滅多に日本語ファイル名などというのは使わないので全然ノーマーク。

NSString:stringWithContentsOfURL:encoding:errorを使って読み込みする際、読み込むファイルのNSURLを単純に生成してました。

NSString *contents = 
  [NSString stringWithContentsOfURL:[NSURL URLWithString:filename] 
  encoding:NSUTF8StringEncoding error:&err];

これだとNSURL:URLWithStringはnilを返しますが、なんのチェックもなしです。(^^ゞなんでもかでも一行に納めるとこういう場合がきつい。ちゃんと2ステップ踏んで、fileスキームもちゃんとエスケープしないとダメですね。

NSURL    *url = [NSURL URLWithString:
  [filename  stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSString *contents = 
  [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&err];

便利なものがあって助かりました。しかし、どうせならエスケープするというのをデフォルトにすればいいのになぁ。などと少し思ったりもします。

すべてのウィンドウ情報を取得する方法。Scripting Bridge vs AX*

in

OSXである時点の表示されているすべてのウィンドウ位置を取得するのはScripting Bridgeで簡単にできると思っていたのですがいざやってみたらうまくできませんでした。やり方は、起動中のすべてのプロセス取得。そのプロセスが持っているウィンドウ情報を引き出すという手法でできると思ったですが...

まず、起動中のプロセス取得。これはシステムイベントから取得できます。

  SystemEventsApplication *systemEvents   = 
  [SBApplication applicationWithBundleIdentifier:@"com.apple.systemevents"];
  SBElementArray *procs = [systemEvents processes];

でこのプロセスからウィンドウ情報を取得します。

for(SystemEventsProcess* proc in procs)
{
   for (SystemEventsWindow *w in [proc windows])
   {
      NSLog(@"%@ :%@ (%.1f:%.1f,%.1f:%.1f),%d,%@,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
            [proc name],
            [w name],
            w.bounds.origin.x, w.bounds.origin.y,
            w.bounds.size.width,w.bounds.size.height,
            w.closeable,
            [[w document] name],
            w.floating,
            w.id,
            w.index,
            w.miniaturizable,
            w.miniaturized,
            w.modal,
            w.resizable,
            w.titled,
            w.visible,
            w.zoomable,
            w.zoomed
         );
     }
  }

これで完璧だと思ったのですが、取得できたのはw.nameだけ。あとは皆NULLかゼロという悲惨な状況でした。プロパティはあるのになんで!

CocoaとC#のデリゲート

以前作成したアプリをv10.6SDKベースでコンパイルしたら警告がでました。何でかなーと思って調べてみるとアプリケーションで使用しているNSRuleEditorのdelegateメソッドがNSRuleEditorDelegateプロトコルに昇格(?)していました。なので、delegateメソッドを実装するクラスは<NSRuleEditorDelegate>しなければいけませんでした。ドキュメント10.6 Symbole Changesを見てみるとNSWindowはじめ多くのdelegateがプロトコル化されていることがわかります。

Objective-C 2.0でプロトコルでもオプション指定ができるようになったので、カテゴリ(非形式プロトコル)定義されていたデリゲートが形式プロトコル化されるのは自然な流れかもしれません。これまではよほど大量のデリゲートメソッドを実装するのでなければ、コントローラクラスの片隅にチョコっとデリゲートメソッド実装していましたが、オブジェクトの独立性や保守性を考えれば、今後はきちんとデリゲート用のクラスを作成するという方向になっていくのでしょう。

デリゲートといえば、前エントリでは嬉々としてデリゲート便利!などと騒いだ手前恥ずかしいのですが、冷静になってみるとInvoke(delegate)はやっぱり単なる「関数ポインタ」と言ったほうがいいのじゃないかと思ったりしてます。設計上の理由によりメソッドの実装を”他オブジェクトに譲(ってもいい|るべきだ)”とすることをデリゲートと呼ぶのであって、たとえばコールバックでの関数ポインタの代替として(文法上)のデリゲートとは意識的に分けて考えたほうが正しいあり方のように思います。

鏡の国では不思議がたくさん

comp.lang.objective-cのConfusion with NSMutableStringのトピックに面白い(表現の)トピックがありました。お題は、以下のコードで、

NSString* path = @"~";
path = [path stringByExpandingTiledInPath];

pathが変更できるのは変じゃない?というものなのですが、それに対して

You haven't understood "Through the Looking Glass" :-)

というリプライがあって何か特別な慣用句なのかショボイ英語力の僕には知れませんが、言い得て妙な表現です。英語圏の人にはこれで「あっ!」って感じで腑に落ちるんでしょうかね。

そういえば、Objctive-Cを始めた頃、こんなコードを書いてしまったことがありましたがこれもhaven't understood "Through the Looking Glass"ですね。

Foo* foo = [Foo alloc];
[foo init];

チェシャ猫は何も言ってくれませんでした(*_*)。鏡の国は、不思議の国...です。

NSRuleEditorのローカライズ(その2)

前回のエントリで書いたようにNSRurleEditorではルールを作成に際してDelegateメソッドを通じて細かい制御をおこなうことができます。必須のDelegeteメソッドは3つ、

  1. ruleEditor:numberOfChildrenForCriterion:withRowType:
  2. ruleEditor:child:forCriterion:withRowType:
  3. ruleEditor:displayValueForCriterion:inRow:

NSRuleEditorのaddRowメソッド呼び出しのタイミングで左辺・述部・右辺についてそれぞれ呼び出されます。今回のサンプルで言えば初回時(ルート)では price/color/sizeの3つの選択肢があるので返す数は3。NSRuleEditorではこの数に基づいて、ruleEditor:child:forCriterion:withRowType:メソッドが3回呼び出します。このメソッドでは左辺3つの情報をNSDictionary形式にパックして返します。パラメータchild:(NSInteger)indexで表示位置を指定してきますのでindex(0)でpriceの情報を、index(1)でcolorの情報・index(2)でsizeの情報を返します。

NSRuleEditorのローカライズ(その1)

数日の間、アップルのサンプルPredicateEditorSampleをベースにNSRredicateEditorに取り組んでいます。複雑なインタフェースを簡単に実装できるのはありがたいのですがローカライズには問題があります。まず、単純なローカライズですがLocalizable.stringでははうまく変換されません。Cocoabuilder/Getting localized NSPredicateEditorによれば、NSPredicateEditorではローカライズファイルを明示する必要があるようで、暗黙にLocaizable.stringを参照してはくれないみたいです。メインバンドルに任意のstringファイルを作成して

[predicateEditor setFormattingStringsFilename:@"predicate.strings"];

として教えてあげる必要があります。また、ポップアップにあたる部分の書式は"%[xxxx]@"というように書きます。

NSPredicateの正規表現

NSPredicateでは正規表現もサポートしていて単にマッチングだけを目的とするのであれば、RegexKiOgreKitを導入する必要はありません。例えばURLマッチングであれば、

NSPredicate* reg = [NSPredicate predicateWithFormat:
  @"SELF MATCHES 'https?://[a-zA-Z0-9/.?_+~=%:;!#-]+'"];
if ([reg evaluateWithObject:source])
  {
     :
   }

Cocoaのメモリ管理関する3つのルール

in

Obujective-C 2.0の時代にアレなんですが、オブジェクトのretain/releaseに関しては時々混乱することがあるので「Learn Objective-C on the Mac」に載っていたCocoaのメモリ管理関する3つのルールをメモ。メカニズムを正しく理解するのも大切だけれど実際コードを書くときにスッっとでてくるガイドラインも大切ということで...

  1. オブジェクトをnew、alloc、copyで生成した場合、release/autoreleseメッセージを送信してオブジェクト解放の責任を負う。
  2. new、alloc、copy以外の方法でオブジェクトを取得した場合オブジェクトはautorelese対象であり、オブジェクト解放のために特になにかする必要はない。(ただし、ルール3には従う必要がある)
  3. もし、オブジェクトにretainメッセージを送信した場合、このretainに対応するrelese/autoreleseメッセージを送信する責任がある。

単純でわかりやすい。普段は1.を気をつけていればいいってことになります。僕が迷うのはアクセッサメソッドの実装で、オブジェクトの生成が離れた箇所に実装されているので迷います。本書にも載っていた、

setSomething:(id)someObject
{
  [someObject retain];
  [myObject release];
  myObject = someObject;
}

とするパタンと、

setSomething:(id)someObject
{
  [myObject release];
  myObject = [someObject copy];
}

のパタンで「アレ? retainするんだっけ?」みたいなヘモい混乱。上の例ならルール3、下の例ならルール1で(たぶん)もう迷わない(と思う)。

UndocumentedGoodness

in

CocoaDevのUndocumentedGoodnessというトピックで、キーチェーンで新しくパスワードを作成する際に使われているPasswordAssistantPanelを使っちゃおうっていうTIPSが紹介されています。この「パスワードアシスタント」はキーチェーンアクセスで「新規パスワード項目」を作成するとき使うツールなんですが、

  • 英単語混じり
  • 文字と数字
  • 数字のみ
  • ランダム
  • FIPS-181準拠

といったルールを選択することでそのルールにあったでパスワードを自動生成してくれてしかも強度まで表示してくれるすぐれものです。忙しい日々テキトーなパスワードを考えるのすら面倒な僕には重宝しそう。

Leran Objective-C on the Mac

in

久しぶり洋書(「Leran Objective-C on the Mac」)を購入。小説なんかと違い技術書ですから存外平易だし、僕程度の英語力でもなんとか読めるところがいいです。iPhoneの人気でCocoa関連の日本語書籍もぼちぼち出回り始めたようですが、まだまだ数は少なくこういった微妙な苦労(楽しみ)は続きますね。

で、この本これなかなか読みやすくて良いです。値段もそこそこ安いし。Objective-Cに関しては前々から勉強し直そうと思っていたので萩原本と併せていい教材になりそう。丁寧に読んでみようかと思っています。

この本、OOPについても記述があってこちらも結構面白い。

Programmers new to object-oriented programming often make the mistake of trying to use inheritance for everything,such as having Car inherit from Engine. (...)
A car is an Engine? Huh? So,use inheritance only when it's appropriate.

とか、茶目っ気たっぷり。

Learn Objective-C on the MAC (Learn Series) Learn Objective-C on the MAC (Learn Series)
Mark Dalrymple


Amazonで詳しく見る
by G-Tools

TigerのNSData:dataWithContentsOfURLは?

in

clippyのtiger版テストをしていて気がついたのだけれどTigerのNSData:dataWithContentsOfURLはちょっと問題がありそう。

NSData   *d = [NSData dataWithContentsOfURL:
  [NSURL URLWithString:@ "http://www.yahoo.co.jp/"]];
NSString *s1  = [[[NSString alloc] 
  initWithData:d encoding:NSUTF8StringEncoding] autorelease];
NSLog(@"s1:%@", s1);

と、次のコード


NSString *s2 = 
  [NSString stringWithContentsOfURL:
    [NSURL URLWithString:@ "http://www.yahoo.co.jp/"] 
    encoding:NSUTF8StringEncoding error:&error];
NSLog(@"s2:%@", s2);

は同じ結果になることを期待していたのですが、NSLog(@"s1:%@", s1);は(nil)となります。同じコードをLeopardで実行すると正しく動作します。ん〜、なんでだろ?

Tiger環境が自由に使えないため詳細を追い切れていません。というわけで、clippy for Tigerのaliasにhttp://〜を使うとうまくありません。(ローカルのfile:///〜はOK。ここがまたわからない...)

原点に進め

in

clippyには、新しいメニュー項目を追加した時、セパレータが表示されず追加前にセパレータのあった位置のメニュー項目がグレー表示されるというなんだか書いていてもよくわからないようなバグがあって、ずっと以前から気づいてはいてチマチマと修正を試みていましたのですがようやく解決しました。

XCodeの64Bit対応

SmultronへのNKFCocoaの組み込み時に発見した「?」な現象。

エンコーディングリストのポップアップを作成する処理で、

NSArray      *encodingsArray = [SMLBasic fetchAll:@ "EncodingSortKeyName"];
NSEnumerator *enumerator     = [encodingsArray objectEnumerator];
id            enc;
while ((enc = [enumerator nextObject]) != nil)
  {
    if ([[enc valueForKey : @ "active"] boolValue] == YES)
      {
        [popup addItemWithTitle :[enc valueForKey : @ "name"]];
        [[popup lastItem] setTag :
          [[enc valueForKey : @ "encoding"] unsignedIntegerValue]];
      }
  }

というコードがあるんですが、このうちポップアップにsetTagするときに最初は、

NKFCocoa.Framework公開します

愛用のエディタSmultronのエンコーディング自動判定に手を入れはじめてからずいぶん時間が経ちますが、年末にフト「nkf使えないかな?」って思い立ってNKFCocoaなるものを作成しました。名前どおりnkfのcocoaラッパーです。Cocoaの開発では日本語関連のメソッドは相変わらずの状況なので、まあ多少の役には立つかなと思います。ベースはnkf-2.0.8です。

Smultronの場合だったら、いままでごちゃごちゃやっていたところを

NSError* error = nil;
encoding = [textData guessByNKF:&error];
if (error != nil)
  {
    NSAlert *theAlert = [NSAlert alertWithError:error];
    [theAlert runModal];
    encoding = 0;
  }

程度に集約できてすっきり。APIの詳細は、ドキュメントを参照してください。

ひとつだけショックだったのはnkfではBOMなしUTF-16の自動判定には対応していなかったですね。まあ、それ以外は概ねやりたいことはできました。お悩みの方、バグ含みですがよかったらお試しください。

Cocoaのプログラミングスタイル

in

この2日間、Error Handling Programming Guide For Cocoaを読んでいるのですがちょっと耳の痛いことが書いてありました。

コンテンツの配信