フォト
2009年11月
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30          
無料ブログはココログ

« 2008年12月 | トップページ | 2009年2月 »

2009年1月

2009年1月30日 (金)

【Cocoa】ウインドウの大きさの取得

 実はアプリケーション設定の保存と読み込みの勉強をしようと思った。保存対象でありがちなのをと思いウインドウサイズなんかいいんだろうとか考えたんだが、ウインドウサイズの取得方法すらわからない。

 そんなわけで今回は、ウインドウサイズの取得方法のまとめ。いろんな記事を参考にはしたのだが、「ズバリこれ!」という記事が見つからず、今回の内容はもしかしたらかなり自己流かもしれない・・・。

 ボタンをクリックすると、そのときのウインドウサイズと位置をログに出力するサンプルアプリを作ってみることにする。

■まずプロジェクト作成

 普通に。Cocoa Applicationで。

■InterfaceBuilderで作業

 MainMenu.xibをダブルクリックしてInterfaceBuilderを起動する。

Sizeget01

 サイズを取得するきっかけが欲しいので、ウインドウ上にボタンを1個配置する。

Sizeget02

 次はコントローラクラスの作成。Libraryの「Cocoa」→「Objects &Controllers」→「Object」を、MainMenu.xib(English)ウインドウにドラッグ&ドロップ。Identityインスペクタで、「Class」のところに「AppController(任意名)」と入力しておく。

Sizeget03

 同じくIdentityインスペクタで、「Class Actions」に「btnClick:(任意名)」を追加。「Class Outlets」に「mainWindow(任意名)」を追加。

Sizeget04

 ここで、AppControllerをファイル化しておく。MainMenu.xib(English)ウインドウでApp Controllerを選択した状態で、メニューバーの「File」→「Write Class Files...」をクリックして、作成したAppControllerをファイルとして保存しておく。

Sizeget05

 続いてアクションとアウトレットの関連付けをしておく。

 ボタンからAppControllerに向かってcontrol+ドラッグ&ドロップ。「btnClick」を選択する。もう一つ、AppControllerから同じウインドウ内の「Window」に向かってcontrol+ドラッグ&ドロップ。「mainWindow」を選択する。

 ここまでできたら、InterfaceBuilderを保存して終了しておく。

■次はXcode側の作業

 まず忘れないうちに、保存したAppControllerの継承元クラスを書いておく。「AppController.h」の「@interface」の行に、「NSObject」を追記する。

【AppController.h】

#import <Cocoa/Cocoa.h>
@interface

AppController : NSObject {
    IBOutlet id mainWindow;

}
- (IBAction)btnClick:(id)sender;
@end

 次にボタンがクリックされたときのコーディング。

 先程アウトレットにNSWindowのオブジェクトを関連付けた。なので、ソースの中では、「mainWindow」としてアクセスすることが可能だ。

 NSWindowにはインスタンスメソッドのなかに「frame」というのがあって、こいつはNSRect型でウインドウのサイズと位置を返してくれる。NSRect型は構造体で、位置を表す構造体とサイズを表す構造体を持っている。

【AppController.m】

#import "AppController.h"

@implementation AppController
- (IBAction)btnClick:(id)sender {
    NSRect rect = [mainWindow frame];
    int x = rect.origin.x;
    int y = rect.origin.y;
    int w = rect.size.width;
    int h = rect.size.height;
    NSLog([NSString stringWithFormat:
        @"x:%d,y:%d,h:%d,w:%d",x,y,h,w]);
}
@end

Sizeget06

 実際に実行してみる。ウインドウのサイズや位置を変えつつボタンをクリックすると、そのたびにx座標、y座標、高さ、幅をログに出力してくれた。

 Macの画面の座標って、左下が(0,0)なんだな。初めて知った。しかも、得られるウインドウの位置情報って、ウインドウの左下隅の座標らしい。Windowsだと左上基準なんだけど。

月の呼び名

 以前ヴァナディールの時間について調べていたときに、月の話も少しだけ調べた。おさらいすると、

  • 満月、新月は月相(月の満ち欠け)に対して付けられた名称。
  • 月齢とは新月からの経過日数を表した物。
  • 月の公転速度が一定でないため、月相と月齢は一致しない。

 といったことを書いた。

 今、改めて月の形と名前の関係についてネットで調べているのだが、月名・月齢・月相の理解が少し足りないような気がしてきた。というか、もしかしたら間違いだったか。

 Wikipediaによると、月相に対する名前は弦(下弦の月とか上弦の月とか)、望(満月のこと)、晦(つごもり。新月の一日前)、朔(新月)の4つを指すらしい。

 で、他の、例えば三日月とか十六夜(十六日月)、居待月(十八日月)などは、月齢ベースの呼び名であるという風に読めた。確かに名前が日数なんだよな。これって月齢の考え方なんだろう。

 でもこの考え方で行くと、新月から7日目あたりにくる上弦の月(半月)と七日月に区別がないように見えてしまうのだが、別のサイトには明らかに七日月と上弦の月の形が違っていたりするのだ。ちなみにヴァナディールでも七日月と上弦の月は別扱いである。

 Wikipediaの方には月名の由来なども記載があるのだが、それを見るとそもそも厳密にどの月相がどの月名かなど決まってはいないのではないかとさえ思ってしまう。

 で、なんで今更この話を蒸し返したかというと、ヴァナディールの月名が英語版ではどのように表記されるのかを調べるためだった。

 地球の月名の英語訳がわかれば検索のキーワードになるか、くらいな気持ちで調べてみたらハマリまくったわけだorz

 FF11の英語版など手元にある訳は無いのでどのくらい正しいのかはわからないが、こんなサイトを見つけた。

http://www.mithrapride.org/vana_time/

 このサイトではヴァナ関連の時間を表示してくれる英語のページなのだが、ここでは満月(full moon)、新月(new moon)、上弦の月(first quarter moon)、下弦の月(last quarter moon)、後はそれぞれの間のWaning Gibbous、Waning Crescent、Waxing Crescent、Waxing Gibbousといった単語が並んでいる。

 月名の数が明らかに日本語版より少ない。

で、この表記と一緒に月の満ち欠けを表すパーセントの数字が併記されていた。なるほど、もしかしたら英語版では満ち欠けをパーセントで表しているのか・・・?

 FF11の英語版がほしいな。本当のところを確かめてみたい。

 実はInkscapeを使って各月相のアイコンを作った。このそれぞれのファイル名を月名の英単語で付けたかったのだがここまで調べて行き詰まり、結局日本語ファイル名で保存してしまった。

 最初っからそうすりゃよかったよマッタクヽ(`д´)ノ

2009年1月29日 (木)

間違いだらけじゃないか

 一応このブログには、あくまで自分の勉強成果とかメモっておきたいこととかを書いておくことを目的としているつもりではある。ただ、ブログとして公開している以上誰かには見られているというのも理解はしているつもり。

 最近Cocoaに関する記事を、自分としては驚くほどの更新頻度で書いているのだが、勉強しながら書いていると後から「あれは間違いだった」と気付くことがやたらと多い。で、訂正を入れても、またその訂正内容が間違いだったと気付いて・・・の繰り返しである。

 いくらメモ書きだからと言って、読んでくれた人に間違った記事で余計な混乱を与えてしまうのは本意ではない。

 どうしようか・・・と悩んだ末、もうタイトルにこのブログの内容が危ないことをかいてしまえww ということにした。

 本日よりこのブログのタイトルは、

「ページアウトされた記憶の残骸 なにぶん勉強中なので間違いがあるかもしれません。」

 となりました(´・ω・`)

 そんな俺は小心者。

 ・・・あ、直せる範囲で記事も直させていただきますm(__)m

2009年1月28日 (水)

【Cocoa】画像リソースの表示

 本日参考にさせていただいた記事はこちら。

http://www.geocities.jp/osx_makuri/cocoatips_old.html

 将来的に必要になりそうなのでアプリで画像を表示する方法を調べた。ファイルシステム上にある画像ファイルではなくリソースとしての画像が前提だ。アプリの中に組み込まれている画像ってことね。

■まずリソースとして画像をプロジェクトに組み込む

Imagetest1

 Xcodeで新しいプロジェクトを作る。Xcodeのウインドウが開くと、左ペインに「Resource」という名前のグループ(アイコンはディレクトリのアイコンになってるが)があると思う。いつもInterfaceBuilderを起動するためにダブルクリックする「MainMenu.xib」があるグループだ。

Imagetest2

 この「Resource」を右クリックするとポップアップメニューが出てくるが、「追加」→「既存のファイル...」と選択すると、ファイルを選択するダイアログが表示される。ここで好きなファイルを選択して「追加」ボタンをクリックすると、プロジェクトに追加されるという仕組みらしい。

Imagetest3

 追加ボタンをクリックした後、エンコーディングやらコピーするのしないのとか言われる。必要に応じて設定するが、一番上の「デスティネーショングループのフォルダに・・・」の項目はオンにしておいた方が無難そう。エンコーディングについては、画像なんだしエンコードも何も無いだろう(´・ω・`)

Imagetest4

 これで、プロジェクトにリソースとして画像が追加された。画像に限らずどんなリソースでもいいんだろうな、たぶん。

■ウインドウの準備

 InterfaceBuilderを起動する。

Imagetest5

 ウインドウ上にイメージ表示用のクラスをドラッグ&ドロップしたいのだが、目的の物はLibraryのObjectsの中にある、「Cocoa」→「View&Cells」→「Input&Values」にあるMacアイコンのデザインのやつ。「Image Well」っていう名前になってるけど、クラス名は「NSImageView」。これをウインドウ上にドラッグ&ドロップ。

Imagetest6

 配置した後、そいつを選択した状態で「Attributes Inspector」を見ると、一番上に「image」という名前のドロップダウンがあるんだが、この中を見ていくと先程追加した画像リソースの名前がリストアップされている。

Imagetest7

 それを選択すると、ウインドウ上にもその画像が表示される。

■本当に追加されてる?

 作成される実行ファイル「*.app」はバンドル形式というんだそうだ。実はこのファイルはファイルじゃなくて実態はディレクトリらしい。

Imagetest8

 できあがった「*.app」ファイルを右クリックして「パッケージの内容を表示」を選択すると、中身をファインダで見ることができる。

Imagetest9

 こうしてみると、確かに追加した画像が含まれていることがわかる。これで一安心。

■プログラムからリソースを選択

 ここは冒頭に書いたサイト様の情報をほぼ丸写し(・_・;)

 自分の中のリソースにアクセスするには、NSBundleというクラスを利用する。まずは、このクラスで自分自身のバンドルを表すオブジェクトを生成する。

 で、このオブジェクトから目的のリソース名を指定して、そのリソースファイルのパスを取得。そのファイルパスを使って、NSImageオブジェクトを生成(今回のリソースは画像ファイルなので)する。

 それをOutletとして指定したNSImageViewに、setImageメソッドでセットする。

【AppController.h】

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject{
    IBOutlet id imgView;
}
- (IBAction)btnRed:(id)sender;
- (IBAction)btnWhite:(id)sender;
@end

【AppController.m】

#import "AppController.h"

@implementation AppController
- (IBAction)btnRed:(id)sender {
    NSBundle *thisBundle;
    NSString *filepath;
    NSImage *img;

    thisBundle = [ NSBundle mainBundle ];
    filepath = [ thisBundle pathForResource:@"test2" ofType:@"jpg" ];
    img = [ [ NSImage alloc ] initWithContentsOfFile: filepath ];
    [ imgView setImage: img ];
}

- (IBAction)btnWhite:(id)sender {
    NSBundle *thisBundle;
    NSString *filepath;
    NSImage *img;

    thisBundle = [ NSBundle mainBundle ];
    filepath = [ thisBundle pathForResource:@"test1" ofType:@"jpg" ];
    img = [ [ NSImage alloc ] initWithContentsOfFile: filepath ];
    [ imgView setImage: img ];
}
@end

 できあがりはこんな感じ。RedボタンとWhiteボタンでそれぞれ異なる画像を表示する。

Imagetestapp

2009年1月21日 (水)

【Cocoa】イベントハンドラはデリゲートで

 前回書いたソースで、とりあえずデジタル時計っぽくなった。なったんだが、実はまだ完全ではなかった。いろいろ調べ倒して、試しにコーディングしてみて解決には至ることができた。なのでその顛末を記録に残しておく。

 振り返ってみると、3つの問題にぶつかった。

■1つめの問題

 まず一つ目は、ウインドウを閉じた後にシステムがエラーを出して、アプリを再起動するかどうか聞いてくるという問題。

 Xcode上で動きを追っかけてみた。

 ログを見ていると、アプリを閉じた瞬間に「フリーされたインスタンスにメッセージを送っている」っぽいメッセージが出て、その後GDBが起動を始める。この時点でアプリは反応しなくなる。dealloc()の中でNSLog()してみたのだが、そのメッセージは出力されない。コントローラの dealloc()に制御が渡る前にエラーになっている?

 アプリケーションとしてはNSTimerを使って0.5秒毎にウインドウ上のラベル表示内容を更新する処理をしている。ラベルに表示するテキストは、コントローラ内の変数をOutletとして設定している。「フリーされたインスタンス」というのがこの変数になるのだが、もしかしてコントローラ自身がフリーされているのにも関わらず動作している?

 もしくは、Outlet先のラベルが既にフリーされている?表示先はフリーされているんだけど、0.5秒毎に書き換え処理が走ってしまうので、このような現象になった?

 以上、全て俺の想像。

 でも確かに、ウインドウ上のコントローラの状態を変更するタイマーを止めるのなら、このウインドウをクローズする直前じゃないとだめな気がしてきた。それであれば状況とつじつまが合うし。

 そこで次の問題は、Cocoaではどうやって(Windowsで言うところの)ウインドウのOnCloseイベントを捕まえるのか、という問題。もっと大きく捉えると、そもそもCocoaでイベントハンドラはどうやって書くのかということか。

 ネット上の調べてみた話を総合すると、デリゲートという仕組みを使うらしい。

 C#にもデリゲートというキーワードがあったような気がする。正直あまり使いこなしていなかったので、C#のデリゲートがどういうものかはわからないが、C#では一種の関数ポインタっぽい物らしい。

 で、どうもCocoaのデリゲートは単純な関数ポインタ以上のことをやってるように見える。

 ウインドウのクローズイベントを捕まえて処理をするケースを想定して、コーディングのしかたを追いかけてみる。

1.登場人物の確認

 まずイベントを起こすオブジェクトと、イベントを受け取って処理するオブジェクトを決める。

 今回の場合、イベント起こすのはInterfaceBuilder上のWindow(NSWindowクラスのオブジェクト)。イベントを処理するのは自分で作成したコントローラクラスとする。

2.コントローラクラスにデリゲートメソッドを実装

 自分で作っているコントローラクラスに、NSWindowクラスのデリゲートメソッドと同名、同戻り値、同引数のメソッドを定義する。これがイベントハンドラになる。

 今回はウインドウのクローズに対して処理したいので、windowWillCloseデリゲートメソッドをコントローラ側に実装すればいい。

- (void)windowWillClose:(NSNotification *)notification

3.デリゲートの設定

 ウインドウのクラス(NSWindow)側のデリゲートにコントローラを割り当てる。これはInterfaceBuilder上でcontrolキーを押しながら、Windowからコントローラに向かってドラッグして接続することでできる。

 この接続をすることで、ウインドウがクローズしようとしたときに、コントローラ側のwindowWillCloseメソッドが呼ばれるようになる。なので、自分で書いたwindowWillCloseメソッドの中に、タイマーを止めるようコーディングすればいい。

 NSWindowには他にもたくさんのデリゲートメソッドがあるが、今回コントローラ側にはwindowWillCloseメソッドしか実装していない。実装しなかった他のデリゲートメソッドはNSWindow側から呼ばれることは無いのか疑問だったが、どうもその心配は無いようだ。デリゲートとして指定していても、実装してなければ呼ばれることは無いらしい。

■2つめの問題

 これはすっかり失念していた。タイマーを生成したメソッドはNSTimerのクラスメソッドで、こいつはautoreleaseしてくれているはず。なので、あえてreleaseする必要が無い。というか、かえってreleaseすると実行時に怒られる。

■3つめの問題

 ここまで対処して、ウインドウをクローズしたときに異常終了する動きは無くなった。

 ところが、ウインドウは画面からいなくなるのだがDock上にはアイコンが残っており、起動を示すランプも点灯している状態だ。そして、そのDock上のアイコンをクリックしても、うんともすんとも言わない。

 ここは、ウインドウを閉じたらアプリケーションを終了させる方向がいいだろうと言うことで、さらにネットを検索した。

 やり方としては、NSApplicationクラスのapplicationShouldTerminateAfterLastWindowClosed(名前長ぇ・・・)というデリゲートメソッドがあり、このメソッドで戻り値をYESとしてやると、ウインドウクローズ→即アプリ終了ということらしい。

- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)anApplication;

 そんなわけで、勉強したばかりのデリゲートがここでも大活躍である。InterfaceBuilder上のApplicationからcontrol+ドラッグでコントローラへドロップ。デリゲートを設定する。コントローラ内に上記のデリゲートメソッドを実装してコーディングは完了。

■すべて解決

 これでようやくまともなアプリになれた。コントローラのソースを貼り付けておく。

【AppController.h】

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject{
    IBOutlet id labelField;
    NSTimer *timer;
}
- (id)init;
- (IBAction)timeUpdate:(id)sender;
- (void) dealloc;
- (void)windowWillClose:(NSNotification *)notification;
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:
                            (NSApplication*)anApplication;
@end

【AppController.m】

#import "AppController.h"
@implementation AppController
- (id)init
{
    self = [super init];
    timer = [NSTimer scheduledTimerWithTimeInterval:0.5
                  target: self
                  selector:@selector(timeUpdate:)
                  userInfo:nil
                  repeats:YES];
    return self;
}
- (IBAction)timeUpdate:(id)sender
{
    id now = [[[NSDate alloc]init]autorelease];
    [labelField setStringValue:[now description]];
}
- (void) dealloc
{// 結局このメソッドは書いておく必要が無いかもしれない。
    [super dealloc];
}
- (void)windowWillClose:(NSNotification *)notification
{// ウインドウクローズ時に、タイマーを停止しておく。
    [timer invalidate];
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:
                           (NSApplication*)anApplication
{// ウインドウがクローズしたらアプリを終了する。
    return YES;
}
@end

2009年1月16日 (金)

【Cocoa】タイマーで1秒ごとに処理をする

 簡単なデジタル時計アプリを作りながらCocoaの勉強ということで、今回はタイマーの勉強。時計だから少なくとも1秒後毎に時間の処理をしたい。

 C++BuilderならTTimerクラス、Visual C#ではTimerクラスというのがあって、時間間隔をプロパティで指定すると、その時間毎にイベントが発生して、そのイベントハンドラを記述するといった感じで作れる。

 Cocoaではどうするかというと、NSTimerクラスというのがあるので、それを使う。例えばこんな感じ。

    NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:0.5
                                    target: self
                                    selector:@selector(timeUpdate:)
                                    userInfo:nil
                                    repeats:YES];

 最初の「0.5」は、0.5秒毎に処理をするという意味。

 「target」には0.5秒経った時に呼び出されるオブジェクトのID、「selector」にはそのオブジェクトのメソッドを指定する。ここでは、このNSTimerが使われているのと同じクラス内(selfは自分自身を表す)の「timeUpdate」というメソッドを呼び出すように指定している。

 このメソッドは、どうも戻り値が「IBAction」型で、引数に「id」型の変数を取るメソッドでないとだめらしい。要はアクションメソッドとして作成されたものを前提にしているようだ。それをselectorというのか?その辺はいまいち勉強不足。

***2009-01-28 訂正***

 selectorとして指定するメソッドは、別に戻り値も引数も関係無いようだ。引数無しの戻り値voidでも、エラーは出なかった。この記事書いたとき何がエラーだったんだろうなぁ・・・。

***2009-01-29 さらに訂正***

 上記の訂正は、コンパイル時はエラーにならないが実行するとunrecognized selectorとかいわれる。

 selectorとして指定するメソッドは、戻り値voidで、引数にNSTimer*型である必要があるらしい。ヘルプをよくよく見たらそう書いてあった。最初間違えたときはid型を引数にしてたからたまたまうまくいってたのか・・・。

***訂正終わり***

 「userinfo」には呼び出すときに渡す情報。呼び出すメソッドの引数として渡すということか?ここでは特に渡す必要もないので「nil」としている。

 「repeats」は、繰り返し呼び出すかどうかのBOOL値。「YES」なら0.5秒毎に繰り返し呼び出される。「NO」だと0.5秒後に1回だけ呼び出される。

 実際に作ってみた。ウインドウ上にはテキストラベルを一つだけ貼り付けた物を用意する。コントローラからは「textField」という名前で関連付けた。

【AppController.h】

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject{
    IBOutlet id labelField;
    NSTimer *timer;
}
- (id)init;
- (IBAction)timeUpdate:(id)sender;
- (void) dealloc;
@end

【AppController.m】

#import "AppController.h"
@implementation AppController
- (id)init
{
    self = [super init];

    timer = [NSTimer scheduledTimerWithTimeInterval:0.5
                  target: self
                  selector:@selector(timeUpdate:)
                  userInfo:nil
                  repeats:YES];
    return self;

}
- (IBAction)timeUpdate:(id)sender
{
    id now = [[[NSDate alloc]init]autorelease];
    [labelField setStringValue:[now description]];
}
- (void) dealloc
{
    [timer release];
    [super dealloc];
}
@end

 これで一応簡単なデジタル時計のできあがり。

 はぁ・・・やっとここまでかよ。先は長ぇなぁ。

**2009-01-21 追記**

 このままでは、まだいくつか動作に問題があった。それの解決までの顛末を次の記事に書いたので、正解はそちらで。

2009年1月14日 (水)

WinwdowsでObjective-C パート2 (NS~クラスも利用可)

 当ブログの元旦の記事で、Cygwinを使ってWindows上でObjective-Cのソースをコンパイル・実行する方法を書いた。だが、この方法だとMac上でよく使う「NS~」で始まるクラス群を利用可能にする方法が見つけられなかった。

 で、最近発見したのがこちら。

ほんまの走り書き技術メモ - WindowsでObjective-C

 こちらの方法だと、まずCygwinがいらない。また「NS~」クラスを使ったソースもコンパイル可能。スバラシイ。

 環境的な部分も補足しつつ、こちらのブログにもやり方を掲載してみる。

■前提環境

 OS : Windows XP SP3

 あ、XPで試したってこと。Vistaではやってない。Vistaでもいいのかも。

■必要なファイルの入手

GNUstepのサイトの、Windows版インストーラ配布ページ

 ここから、

     
  • GNUstep System 0.19.2
  •  
  • GNUstep Core 0.19.2
  •  
  • Calculator.app Version 1.0.0-2
  •  
  • Gorm.app Version 1.2.4
  •  
  • SystemPreference?.app 1.0.2-2

 を、ダウンロードする。

■インストール

 ダウンロードしたファイルは全てexeファイル。とりあえず上記の順番に実行して、普通にインストールした。

 インストール先は「c:\usr\GNUstep」としてみた(デフォルトは「c:\GNUstep」にインストールしようとする)。

 インストール時に環境変数PATHを書き換えているのが確認できたため、インストール完了後システムリブートを実施。

■コンパイル

 なにやらコンパイルオプションをいっぱい指定する必要があるらしいので、バッチファイルを作ってみた。ヘッダやライブラリを指定するパスは、GNUstepをインストールした場所に合わせて適宜書き換える必要がある。

【cl.bat】

gcc -o %1 %1.m
-I c:/usr/GNUstep/GNUstep/System/Library/Headers
-L c:/usr/GNUstep/GNUstep/System/Library/Libraries
-lobjc
-lgnustep-base
-fconstant-string-class=NSConstantString
-enable-auto-import

 見やすいように改行を入れたが、実際には改行は入っていない。

 使い方は、例えばtest.mというソースをコンパイルするときは、

 D:\> cl test

 とすると、test.mをコンパイルしてtest.exeを出力してくれる。

【Cocoa】NSDateから日時の値を取得する

 NSDateオブジェクトが持っている日時情報から、年月日時分秒の各数値を取り出す方法を調べた結果をメモしておく。

 今回の記事は正直自分自身理解しきれていない。が、一応取得はできたのでこれはこれで使えることを期待したい。・・・というかこの方法って、本当にこれでいいんだろうか。もっとスマートな方法があるような気がしてならないんだが・・・。

 以下のソースは、ウインドウの上に配置されているボタンのアクションメソッドを想定している。このアクションメソッドが実行されると、同じくウインドウ上に配置されているテキストフィールド(textField)に文字列を表示する処理をしているつもり。ソースは、こちら(AppleのDeveloper Conection上のドキュメント)にあったソースを参考にした。

1:- (IBAction)btnOnClick:(id)sender {
2:     NSCalendar* cal = [[NSCalendar alloc]
3:                       initWithCalendarIdentifier:NSGregorianCalendar];
4:     unsigned int unitFlags = NSYearCalendarUnit |
5:                              NSMonthCalendarUnit |
6:                              NSDayCalendarUnit |
7:                              NSHourCalendarUnit |
8:                              NSMinuteCalendarUnit |
9:                              NSSecondCalendarUnit;
10:  NSDateComponents *components =
11:    [cal components:unitFlags fromDate:[NSDate date]];
12:  int year = [components year];
13:  int month = [components month];
14:  int day = [components day];
15:  int hour = [components hour];
16:  int minute = [components minute];
17:  int second = [components second];
18: NSString* str = [NSString stringWithFormat:@"%d/%d/%d %d:%d:%d",
19:                             year,month,day,hour,minute,second];
20:  [textField setStringValue:str];
21:  [cal release];
22:}

 まず2行目でNSCalendarクラスのオブジェクトを生成している。使用している初期化メソッドは「initWithCalendarIdentifier」だ。これは、生成されたカレンダーオブジェクトを初期化しているのだが、このときどういう暦で初期化するのかを言うことができる。ここでは「NSGregorianCalendar」を指定している。いわゆるグレゴリオ暦だ。

 ヘルプを見るとこのほかにNSBuddhistCalendar(仏教歴?)とかNSIslamicCivilCalendar(イスラム歴?)とか指定できるらしい。

 4行目のunitFlags変数は、11行目で使用する。NSCalendarクラスのcomponentsメソッドで、年月日時分秒のどの値を取り出すかを指定するために使用するらしい。

 10行目でNSDateComponentsのオブジェクトへのポインタの宣言、11行目以降でNSDateComponentsオブジェクトの生成を行っている。NSDateComponentsクラスは、単純に年月日時分秒の値を保持するためのクラスのようだ。ヘルプ上、そのほかに機能は持っていないように見えた。

 NSDateComponentsオブジェクトの生成には、NSCalendarクラスのcomponentsメソッドを使用する。4行目で作ったフラグと、取り出したい元の日付を表すNSDateオブジェクトを指定する。このソースでは、11行目に[NSDate date]と指定しているので、現在の日時を処理の対象にしている。

 12行目から17行目までは日時値の取り出し。各々int型の変数に取り出している。

 18行目、19行目では、取り出した値を使って日時文字列を作っている。

 20行目は、生成した文字列をテキストフィールドに格納。

 21行目は、NSCalendarオブジェクトをallocで生成したので、忘れずにrelease。

 とまぁこんな感じなんだが・・・本当にこんな面倒な方法しかないのか?(・_・;)

2009年1月12日 (月)

【Cocoa】メモリ管理勉強中(改訂版)

 Borland C++ Builderで作ってた時は、自分でnewしたものはすべて自分でdeleteしていれば良かった。Visual C#なぞnewした後はほったらかして自分で消さなくてもフレームワークの仕組みで消してくれていた。ガベージコレクションと言うやつだ。

 CocoaにはCocoaのメモリ管理のやり方があるらしい。ネットで検索すると、その辺の記事はたくさん引っかかるので読んではみるものの、正直わかったようなわからんような。

■Objective-Cのガベージコレクション

 Objective-Cにもガベージコレクションの機能があるらしい。

     
  • Objective-C 2.0からの機能。これはLeopard以降ってことを意味しているらしい。
  •  
  • Xcodeのプロジェクトの設定で有効無効を切り替える。デフォルトは無効。メニューバーの「プロジェクト」→「プロジェクト設定を編集」で表示されるダイアログの「ビルド」タブ内「Objective-C Garbage Collection」で変更可能。
  •  
  • ガベージコレクションを使っていれば、動的に作成したものは参照が無くなった後いつか勝手に回収してくれる。Visual C#の時と同じイメージでいいらしい。

■ガベージコレクションを使わない場合

 勉強も兼ねて、当面ガベージコレクションを使わないでやってみることにした。ガベージコレクションが機能追加される前は、参照カウンタを使った管理方式だったらしい。

     
  • 作成されたオブジェクトの参照カウンタが0になったら、オブジェクトが解放される。
  •  
  • オブジェクトの参照カウンタの上げ下げは「retain / release」メソッドを使う。NSObjectのメソッドなので、NSObjectから継承している限りどれでも使える。
  •  
  • 自分でオブジェクトを作成する場合は、「[[Foo alloc]init]」としておけば参照カウンタは1になる。
  •  
  • 他人が作ったオブジェクトであっても、自分が参照する必要があり勝手に解放されたくない場合は、「retain」しておけばよい。その場合、用が済んだら「release」しておくこと。
  •  
  • 一つの関数内で作成してすぐ解放するとわかっているものであれば、オブジェクト生成の際に「autorelease」メソッドを呼んでおく。こうすれば使い終わった時点で自動的にreleaseしてくれる。「[[[Foo alloc]init]autorelease]」という感じ。
  •  
  • Cocoaで提供される様々なクラスでは、そのクラス自身のオブジェクトを生成するクラスメソッドが用意されていることがある。それらが生成するオブジェクトは、alloc / init +α(+αはモノによるかも)の処理を行い、さらに自動的にautoreleaseしてくれる。

■実際やってみた

 簡単なアプリで実践してみることに。

 一つウインドウを用意する。ウインドウ上にはボタンが一つと、テキストフィールドを配置しておく。ボタンのアクションメソッド内には、日時を取得して、文字列化したものをテキストフィールドにセットする。

 日付の取得は「NSDate」クラスを使用する。NSDateオブジェクトを生成する方法としてはalloc / initを使う方法の他に「date」というクラスメソッドもある。どちらも生成するオブジェクトを現在日時で初期化する。

 アクションメソッドの内容を、いくつかパターンを変えて作ってみた。

【alloc / initした場合】

- (IBAction)timeUpdate:(id)sender
{
    id dt = [[NSDate alloc]init];
    [textField setStringValue:[dt description]];
    [dt release];
}

【alloc / init / autoreleaseした場合】

- (IBAction)timeUpdate:(id)sender
{
    id dt = [[[NSDate alloc]init]autorelease];
    [textField setStringValue:[dt description]];
    // この場合は[dt release]する必要は無い。
    // オブジェクト生成時にautoreleaseしているので。
}

【dateメソッドを使った場合】

- (IBAction)timeUpdate:(id)sender
{
    id dt = [NSDate date];
    [textField setStringValue:[dt description]];
    // この場合は[dt release]する必要はない。
    // dateメソッドがautoreleaseしてくれる。
}

 NSDateクラスの用意されている「distantFuture」、「distantPast」も、この「date」メソッドと同様autoreleaseまでしてくれる。

 自分で確保したオブジェクトのうち、自分で明示的にreleaseしなければならないのは、「alloc / initしたもので、かつautoreleaseしてないもの」ということになる。

 参照カウンタ方式が便利なのは、release=即解放じゃないってとこかな。

 C#やC++で作っていたときは、自分が参照しようとしたときに他の処理で解放されてしまわないように、確保と解放のタイミングにかなり気を遣ったような気がする。

 こっちの方式ならその心配もないわけで。

【Cocoa】NSDateについてお勉強

 今日の本題に入る前に・・・。前回の記事の訂正をいれた。なにぶん勉強中で嘘記事書いているかもしれないので、ご容赦いただきたい。>読んでいただいた方々へ

 毎回記事の冒頭にこの台詞書いとくかな・・・

 さて今回はNSDateクラスの機能をもう少し詳しく勉強してみることにする。

■初期化

 前回の記事に書いた通りNSDateにはいくつかの初期化メソッドがあり、「date〜」で始まるメソッドと「init〜」で始まるメソッドに分かれる。

 基本的に「date〜」で始まる方はalloc / initしてくれるもので、かつクラスメソッド・・・つまりオブジェクトを生成しなくても利用できるメソッドらしい。

     
  • + date:
  •  
  • + dateWithNaturalLanguageString:
  •  
  • + dateWithNaturalLanguageString:locale:
  •  
  • + dateWithString:
  •  
  • + dateWithTimeIntervalSinceNow:
  •  
  • + dateWithTimeIntervalSinceReferenceDate:
  •  
  • + dateWithTimeIntervalSince1970:

 逆に「init〜」メソッドの方はオブジェクトメソッド・・・つまりオブジェクトが生成されていないと利用できないメソッドらしい。つまりallocされていることが前提というわけだ。

     
  • – init:
  •  
  • – initWithString:
  •  
  • – initWithTimeIntervalSinceNow:
  •  
  • – initWithTimeInterval:sinceDate:
  •  
  • – initWithTimeIntervalSinceReferenceDate:

 こうやってみると、自分で特定の日時を値として指定して初期化するメソッドが存在しないことに気付く。

 これは少し不満だなぁ・・・。

 「WithString」系、「WithNaturalLanguageString」系は、日時を表す文字列をネタにしてNSDateオブジェクトを初期化する。例えば「2009-01-01 14:52:00 +0900」といった文字列なんかで初期化する。

 逆に言うと、一回自分で文字列を作る手間が必要だってことだ。

 「TimeInterval」系は、特定の日時からのずれを指定して初期化する。例えば現在からX秒前とか、ReferenceDate(2001-01-01固定)からX秒後とか。後は好きな日時をNSDateオブジェクトで与えておいて、そこからX秒前とか。

 日時データを整数で持っていたとしても、そこからNSDateオブジェクトを作るのなら一度文字列化をする手間が発生する覚悟をしないといけないかもしれない。

■最大と最小

 遠い未来(ヘルプによると「distant future」と表現されている)と遠い過去(ヘルプによると「distant past」と表現されている)のNSDateオブジェクトを生成するクラスメソッドが存在する。

  • + distantFuture
  • + distantPast 

 実際にどういう値が帰ってくるか確認してみた。

    id dt = [NSDate distantPast];
    [textField setStringValue:[dt description]];

 これで表示される内容は、「0001-01-01 09:00:00 +0900」だった。西暦元年から扱えるらしい。これは正直嬉しいな。

    id dt = [NSDate distantFuture];
    [textField setStringValue:[dt description]];

 これで表示される内容は「4001-01-01 09:00:00 +0900」と出た。少なくとも西暦4000年まで扱えるなら、ほぼ最大値を気にすることは無さそうだ。

■文字列化

 上の例で既に使用しているが、NSDateオブジェクトが保持している日時を文字列として取り出すメソッドが存在する。

  • – description:
  • – descriptionWithCalendarFormat:timeZone:locale:
  • – descriptionWithLocale:

 descriptionメソッドは「yyyy-mm-dd hh:mm:ss ±hhmm」の形式のNSStringオブジェクトを返す。

■時間の加減算

 現在NSDateオブジェクトが保持している日時からのずれ(秒単位)を指定して、新しいNSDateオブジェクトを生成するメソッドがある。

  • – addTimeInterval:

■NSCalenderDateオブジェクトへの変換

 年月日を数値として扱うには、NSCalenderDateオブジェクトを利用することになるらしい。NSCalenderDateクラスについてはまた別の記事にまとめる予定。

 さんざん計算はNSDateでやっておいて、最後に表示するところで始めてNSCalenderDateに変換するという順番かな、使うとしたら。

  • – dateWithCalendarFormat:timeZone:

■感想

 任意の日時で初期化する場合にどうしても文字列の日時データが必要なことには抵抗があるが、概ね俺がやりたいと思っている機能は持っ ている模様。

2009年1月 7日 (水)

【Cocoa】メモリ管理勉強中

 勉強中とはいえかなり嘘っぱちな記事だったことがわかったため、いったん削除する。

 改めて別記事で書き直したので、そちらを参照願いたい。

【Cocoa】メモリ管理勉強中(改訂版)

2009年1月 5日 (月)

XcodeでCocoaアプリの作成

 以前ネットで見つけたXcodeのチュートリアルムービーと、先日買ってきたたのしいCocoaプログラミングという本に書いてある内容では、微妙にではあるがアプリの作る手順が違っていることがわかった。で、日本語の解説がついてる本の方をベースに勉強してみることに。

簡単にまとめると、おおよそ以下のような手順になっている。一応手元のXcodeで確認済み。ちなみに手元のXcodeのバージョンは3.1.2。

     
  1. Xcodeを起動。
  2.  
  3. メニューバー「ファイル」→「新規プロジェクト」をクリック。
  4.  
  5. 「Cocoa Application」をダブルクリック。
  6.  
  7. プロジェクトの名前を入力。保存するディレクトリも選択しておく。
  8.  
  9. ここから、コントローラオブジェクトの作成。メニューバー「ファイル」→「新規ファイル」をクリック。
  10.  
  11. カテゴリー「Cocoa」にある「Objective-C class」をダブルクリック。
  12.  
  13. ファイル名に「AppController.m」を指定して「完了ボタン」をクリック。
  14.  
  15. 「AppController.h」に手書きで、プロパティ「IBOutlet id textField;」と、メソッド「-(IBAction)sayHello:(id)sender;」を入力。
  16.  
  17. ここでメニューバー「ファイル」→「保存」をクリック。
  18.  
  19. Xcodeの左ペイン内「Resources」→「MainMenu.xib」をダブルクリックしてInterfaceBuilderを起動。
  20.  
  21. Libraryウインドウの「Cocoa」→「Objects&Controllers」にある「Object」(青い立方体のアイコン)を「MainMenu.xib」ウインドウにドラッグ&ドロップ。
  22.  
  23. ドラッグ&ドロップした「Object」を選択している状態でインスペクタウインドウ(ウインドウの上の方にタブが7つほど表示されているウインドウ)の右から二つ目「i」のタブ(Object Identity)をクリック。
  24.  
  25. 同ウインドウの上部「class」で先ほど作成した「AppController」を選択。
  26.  
  27. 次にインターフェースの作成。InterfaceBuilder起動時に表示されている空のウインドウに、LibraryウインドウからButtonとText Fieldをドラッグ&ドロップ。
  28.  
  29. 次にインターフェースとコントローラの関連付け。「MainMenu.xib」ウインドウの「AppController」からインタフェース上のText Fieldに対してcontrol+ドラッグ&ドロップ操作。ドラッグ中は青いラインが表示される。
  30.  
  31. ドロップするとアウトレットとしてtextFieldを選択できるので、それを選択。
  32.  
  33. インターフェース上のButtonから「MainMenu.xib」ウインドウの「AppController」にcontrol+ドラッグ&ドロップ操作。ドラッグ中は青いラインが表示される。
  34.  
  35. 今度はアクションとしてsayHelloを選択できるので、」それを選択。
  36.  
  37. 次はsayHelloのアクション内容を記述する。Xcodeに戻って、AppController.mを開く。
  38.  
  39. 「@implementation」内に「-(IBAction)sayHello:(id)sender」メソッドを記述する。内容としては「[textField setStringValue:@"Hello World !"];」といった感じ。
  40.  
  41. Xcode上部にある「ビルドして進行」ボタンをクリックして、コンパイルと実行する。

 少々長くなったが、おおざっぱに書くとこのような内容だった。

 チュートリアルムービーでは、上記手順の6番のコントローラクラスを作成する際に、一度新規にObjective-C classファイルを作成しておいて、InterfaceBuilderからメニュー「File」→「Write Class File」を選択していた。

 どちらの手順も、俺には少々不自然に見えた。何で手動でクラスファイルを作らないといけないのか・・・。本の方に至っては、アクションやアウトレットまで手動で記述する内容になってるし。Borland C++ BuilderやVisual C#ではそんなことをする必要が無かったから。

 自分の納得できそうな手順でもう一度アプリを作成してみる。以下手順だが、2度目なので詳細は省いた。

     
  1. Xcodeを起動する。
  2.  
  3. 新規プロジェクトを作成する。
  4.  
  5. 「MainMenu.xib」をダブルクリックしてInterfaceBuilderを起動する。
  6.  
  7. 空のウインドウにボタンとテキストフィールドを配置する。
  8.  
  9. LibraryウインドウからObjectをMainMenu.xibウインドウにドラッグ&ドロップ。
  10.  
  11. ドラッグ&ドロップしたObjectを選択した状態で、インスペクタウインドウへ。
  12.  
  13. 「Object Identity」でclassに「AppController」を入力。
  14.  
  15. Class Actionのところで「+」ボタンをクリック。「sayHello:」を入力(最後のコロンを忘れずに)。
  16.  
  17. Class Outletsのところで「+」ボタンをクリック。「textField」を入力(こちらはコロンは不要)。
  18.  
  19. メニューバー「File」→「Write Class Files」を選択して、AppController.mとして保存する。「Create '.h' file」もチェックしておく。
  20.  
  21. 保存ボタンをクリック直後に、プロジェクトに加えるためのダイアログが表示されるので、自分が作成したプロジェクトにチェックを入れて「Add」ボタンをクリック。
  22.  
  23. Xcodeに戻る。プロジェクトに先ほど作成したAppController.mとAppController.hが追加されている。
  24.  
  25. AppController.hを開く。クラス定義で継承元クラスが記述されていないので、自分で継承元クラスとして「NSObject」を記述しておく。
  26.  
  27. AppController.mを開く。メソッド「sayHello」の動作を記述する。内容としては先程と同様に「[textField setStringValue:@"Hello World !"];」。

 手順10で、InterfaceBuilder上で継承元クラスを指定する場所がどうしても見つからなかった。なのでここだけは手動でソースに直接継承元クラスを記述しなければならなかったが、まぁ概ね俺のイメージに近い作成手順になった。

2009年1月 4日 (日)

Xcodeをバージョンアップ

 今日、本屋に行って「楽しいCocoaプログラミング」という本を買ってきた。

 早速読んでみると、この本ではXcodeのバージョンは3.1が前提だと書いてある。俺がインストールしているのは、iMacについてきたDVDに入っている3.0だ。

 Xcodeの3.1はネットからダウンロードできるらしい。

■まずはApple Developer Connectionにアクセス

 urlはここ

 ページの下の方に「Developer Tools and Technologies」というのが見えたので、「さらに詳しく」をクリック。

 次のページの真ん中あたりに「Xcode Tools」というのがあるので、「さらに詳しく」をクリック。

 次のページの左側に、「Xcode 3 Free Download」があった。これか。

 次に表示されるページが英語のページになった。「Xcode for Mac Development」と「Xcode for iPhone Development」がある。ほしいのはもちろん「Xcode for Mac Development」の方。「Download Now」をクリック。

■メンバーサイトにログイン

 次に出てきた画面でAppleIDとパスワードの入力を求めている。AppleIDは持っているので、それを入力してログイン。

 次に名前やら住所やら入力を求められるので入力。

 そしてやっとダウンロードできるページに到達。本日現在の最新は3.1.2となっている。買ってきた本で言っている3.1よりも新しいのか・・・まぁいいか。そいつをダウンロードすることに。

 ・・・約1GBを1時間15分ほどでダウンロードなんて出やがった。続きはまた今度。

2009年1月 3日 (土)

Objective-C オブジェクトの生成、メソッドの呼び出し

 

こちらにCocoaフレームワークのリファレンス和訳版があったので活用させていただくことに。

 前回に書いたCTestクラスを想定して記述している。

■オブジェクトの作り方

 オブジェクトの生成、クラスのインスタンス化など、言い方はいろいろあるが。

C++の場合

CTest *obj;

obj = new CTest();

Objective-Cの場合

id obj;

obj = [[CTest alloc] init];

 C++の方はCTest型のポインタ変数objを用意して、new演算子で生成したCTestクラスのオブジェクトのアドレスをobjに格納している。

 Objective-Cの方はまずid型のobjという変数を用意している。id型というのは、オブジェクトを表す汎用型。void*のようなものだと理解するといいらしい。

 [CTest alloc]は、CTestクラスのメソッドallocを呼び出す記述。Objective-Cでは「メッセージを送る」という言い方をするらしいが、まだ自分的にまだ理解しにくいと思うのでC++的な言い方にしておく。このallocメソッドは、ルートオブジェクトに定義されているオブジェクト生成のメソッド。

 [[CTest alloc] init]は、[CTest alloc]で生成されたオブジェクトのinitメソッドを呼び出している。newメソッドはルートクラスに定義されているものをCTestクラスでオーバーライドしているもの。

 allocとinitの二つが、C++のnew演算子の処理に相当すると理解している。

 で、[[CTest alloc] init]は、[CTest new]という書き方をしてもいいらしい。

2009年1月 1日 (木)

Objective-C クラスの宣言まで

 現在、Wisdom SoftというサイトのObjective-C入門を参考にしながら、Objective-Cの勉強中。理解できたところをまとめてみる。

■言語仕様

 基本的にはC言語をベースにしてSmalltalkを取り入れたものだそうだ。C++とは異なる方向性で実現されたオブジェクト指向言語ということらしい。それ故、C++に見慣れてきた俺の目には、Objective-Cのソースはかなり違和感のあるソースに見えた。

■ソースファイルの拡張子

 C++だと「*.cpp」が一般的だと思うが、Objective-Cだと「*.m」となる。これはもう何も考えず、「そうなんだ」と理解するしかないだろう。

■ヘッダファイルのインポート

 いろいろ見ていると、Objective-Cでは「#include」は使わずに「#import」を使うのが一般的みたい。

 機能的には、一度読み込んだものをもう一度読み込むか読み込まないかの違い。#importの方は一度読み込んだら同じものをもう一度読み込むことは無い。

 従来のヘッダファイルでも、標準提供のものであれば#ifdefを使って二重読み込みを防止しているけど、#importを使えばそういうことを気にしなくてもよくなる。

 プログラムを記述する際、必ず読み込まなければならないヘッダファイルが存在する。gccの場合は「objc/Object.h」、Cocoaの場合は「Foundation/NSObject.h」となる。

■クラスの作り方

 C++とObjective-Cで、同じことを行なうクラスの宣言を書いてみた。クラス自体はあまり意味の無い簡単なものである。C++の方はMFCを、Objective-Cはgccを想定して書いてある。両方とも、

  • - ルートクラスを継承したCTestという名前のクラスの宣言。
  • - int型のaというプライベートプロパティを持っている。
  • - このクラスのオブジェクトが生成されるときに、プロパティaが1で初期化される。
  • - int型の変数を引数に取りint型の値を返す「test1」というメソッドを持っている。

 といったクラスである。

 あと、C++のソースは未検証。うろ覚えで書いてるので、もしかしたらsyntax errorくらい出るかもしれないがご容赦を。

C++の場合

class CTest : CObject
{
private:
    int a;
public:
    CTest(void);
    int test1(int i);
}

CTest::CTest(void)
{
    this->a = 1;
}

int CTest::test1(int i)
{
    return i+a;
}

Objective-Cの場合

@interface CTest : Object
{
    int a;
}
- (id)init;
- (int)test1:(int)i;
@end

@implementation CTest
- (id)init
{
    [super init];
    self->a = 1;
}
- (int)test1:(int)i
{
    return i + a;
}
@end

 一目見てかなり違うのがわかる。これが、俺が感じた違和感だった。

C++ Objective-C
クラスの宣言の仕方 classキーワードを使って構造体を宣言するような感じで記述。 @interface~@endというキーワードを使って宣言。
ルートクラス MFCだとCObject、VCLならTObject。 gccならObject、CocoaならNSObject。
コンストラクタ クラス名と同名のメソッド。オブジェクト生成時に自動的に呼び出される。 C++のようなコンストラクタは存在しない。初期化が必要な場合は自分でメソッドを定義して、生成時に自分で呼び出す必要がある。通常はルートクラスが実装しているメソッド「init」をオーバーライドする。
オブジェクト生成に必要なメモリの確保 new演算子がメモリの確保をしてくれる。すなわち、呼び出す側がnew演算子を使ってインスタンスを生成する。 ルートクラスに実装されているallocメソッドを使ってインスタンスを生成する。すなわち、呼び出される側がインスタンスを生成するメソッドを提供する。
プロパティとメソッドのアクセス制御 privateキーワード、publicキーワードを使って制御する。 プロパティはプライベート、メソッドはパブリック扱いとなるらしい。外部からプロパティにアクセスする必要がある場合は、必ずメソッドを用意する。
メソッドの定義 クラス名::メソッド名() @implementation~@endというキーワードを使って定義する。先頭にハイフン必須。
- (戻り型)メソッド(引数型)引数

Objective-CをWindowsで利用するために

 Cocoaの勉強を進めるにはObjective-Cの勉強をした方がよさ気。なんせCocoaのメインになる言語らしいし。

 会社にMacは置いてないが、Cygwinを使えば仕事PCのWindows上でもObjective-Cのコンパイラを利用することが可能らしいので、実際にやってみた。

■Cygwinを入手

 こちらから、Cygwinのインストーラ「setup.exe」を入手する。このインストーラはネットワークインストールが可能で、いつでもコンポーネント毎にインストール・アンインストールが可能となっている。

 一回インストールした後に追加して他のコンポーネントもインストールしたい場合でも、このインストーラを起動するだけ。ただしたまにインストーラそのものがバージョンアップしている場合があり、その際はインストーラの再入手が必要。

■Objective-Cのインストール

 ダウンロードしたインストーラを起動し、Cygwinのセットアップをする。途中インストールするコンポーネントを選択するところで、カテゴリ「Devel」の中にある「gcc-objc: ObjC Compiler」を選択する。選択すると同時に他のもいくつか自動的にチェックが入るが、たぶん必要なものなんだろう。

 このあと指示通りにセットアップを進めていくと、必要なものを勝手にダウンロードしてインストールしてくれる。

 ちなみに、このブログで最近書いていたユーザIDとパスワードを求めるproxyについては、このインストーラはちゃんと対応してくれていた(笑)

■Cygwinの起動

 デフォルトでデスクトップ上にCygwinの起動アイコンが作られたと思う。そいつで起動可能。コマンドラインのような窓が開かれるが、プロンプトはbashのとなっている。これがCygwinの端末となる。

■コンパイルのやり方

 利用するコンパイラはgcc。ただ、ソースファイルの拡張子は「.m」にして、objcライブラリを追加してコンパイルするのがミソ。これをすることでObjective-Cとしてコンパイルしてくれる。

$ gcc hoge.m -o hoge -l objc

■サンプルソース

#import <stdio.h>
#import <objc/Object.h>

int main(int argc,char *argv[])
{
    printf("Hello World.\n");
    return 0;
}

 これで一応エラーも無くコンパイルも実行もできた。

KompoZerでソースに意図しない改行が入る

 前々回に書いたMacOSXでも動作する使い勝手のよさそうなHTMLエディタ「KompoZer」だが、使っている内に一つ問題が出てきた。

 WYSIWYG編集をした後にソースを編集しようとしてソース画面を開くと、ソース内の意図しないところに勝手に改行が入るのだ。KompoZerで作成した記事をそのままココログの記事投稿に突っ込むと、ソースで改行されているところは空白として表示されてしまう。なんか気持ち悪い、、、

 早速ネットで検索。KompoZerではHTMLソース上勝手に改行が入るとの記事をいくつか見つけることができた。NvuやNetscape Comopserではこういう動作はしていなかったらしい。

 さらに見ていくと、回避手段に関する記事を見つけることができた。

 インストールされているファイルの内、「editor.js」、「all.js」について、以下のような書き換えをすることで回避できると書いてある。

  • 【修正前】pref("editor.htmlWrapColumn", 72);
  • 【修正後】pref("editor.htmlWrapColumn", 1000);

 つまり、勝手に72バイト目のところで改行していたということなのか。この変更で、1000バイト目で勝手に改行するようになるという話のようだ。

 記事によると、この対処をすることでXHTMLならば勝手な改行が入らなくなると書いてあった。今日は実は仕事先のためWindowsしか手元に環境が無いので、この回避方法をWindowsで試してみた。

 が、結果はXHTMLのTransitional/Strict共にNG。何がいけないのやら。

 これとは別に、Seamonkeyを利用して回避している人の記事も見つけた。それも早速インストールして検証開始(Windowsで)。

 SeamonkeyのComposer設定内で「ソースの変更を最小限に抑える」のチェックボックスをオンにすると余計な改行はしなくなる。が、惜しいのはSeamonkeyのComposerはHTML4のみ。

 あとはMozilla_Correctというアプリを使って整形する手段くらい。ソースを保存後にこいつを通すと綺麗に整形してくれるそうだ(未検証)。KompoZer単体ではもうどうしようもないらしい。

 だんだんホームページビルダーが恋しくなってきたが、ここはガマン。このくらいの手間ならまだなんてことはない。

« 2008年12月 | トップページ | 2009年2月 »