フォト
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          
無料ブログはココログ

Mac

2009年10月23日 (金)

ニューiMac

 新しいiMacのお知らせメールが来てた。

 Windows7にぶつけたんだろうな。

 今度のラインナップ、また画面のサイズがでかくなってる・・・どんどんでかくなるなぁ。CPUもとうとう最上位はCore i5/i7だし。

 俺のは買ってからまだ1年経ってないし買い換えとかはまだ無いが、Core i5//i7が下位機種にも使われ出したくらいが丁度いい頃合いかもね。そんときゃぁ今のを下取りしてもらって・・・値段付くのかねぇこれ。

2009年9月 6日 (日)

Snow LeopardでMySQL事始め

 今回はあんまり内容が無いんだが、一応報告ってことで。

 MacでMySQLの勉強してみたいと思ってMacにapache、php、mysqlのセットアップしてみた。参考にしたサイト様はこちら。

http://www.futomi.com/lecture/macosx/index.html

 ほぼ書いてある通りに作業して問題無くできてしまった。サイト様の内容もMacの方も、まぁしっかりよくできてるのね。

 サイト様の方はLeopardで説明されていて俺も最初はLeopardで試してたのだが、もたもたしてるうちにSnow Leopard発売w

 ってわけで、また最初から今度はSnow Leopardで作業し直した。

 ・・・けどやっぱり問題無くできてしまった。サイト様の内容もMacの方m(以下同文

 ちなみに作業時点のSnow Leopardのバージョンは10.6(.0?)。apache、phpはSnow Leopardに入ってる物をそのまま使用した。MySQLの方は最新版をダウンロード。

  • Apache 2.2.11
  • php 5.3.0
  • MySQL 5.0.85(x86 64bit版)

 サイト様の解説ではPerlでも使えるようにしたりもしていたが、俺の方はphpでDBを操作できればよかったので試していない。ただ、一応順番にCGIのセットアップとかも読んだ方がよさげ。redhat系はいじりなれてるつもりだけど、ApacheとかPHPのいろんなパスが微妙に違ってるっぽい。OS Xでこの辺の作業が初めての俺には役に立った。

 とりあえずphpからMySQLにアクセスできたので、次はでっかいデータをMySQLに突っ込んで遊んでみるかな・・・

2009年8月31日 (月)

Snow Lepardげっと

 前回「DBを!」などと意気込んでみたが、その前にSnow Leopardの発売だw

 ここは思い切ってクリーンインストールしてみることにした。ライブラリ配下もゴミだらけになってたし。

 ネット見てたら、カーネルの起動モードについてこんな記事を見た。

http://journal.mycom.co.jp/articles/2009/08/21/snowleopard/index.html

 とりあえずiMacはデフォルトで32bitカーネルが起動されるらしい。

 システムプロファイラで「64ビットカーネルと拡張機能」のところで現在のカーネルモードを見分けられるっぽい。試しに「6」と「4」のキーを押しっぱなしで起動してみたら、ちゃんと64ビットモードになった。・・・んだがあんまり違いがわからんね^^;

 元々サクサク動いてくれてたしねw

 カーネルモードとは関係なく、システムの終了はえらい劇的に早くなってないか? 本当に終了処理ちゃんとしてるよなw

 あと、一応ATOK2008も動いてくれているんだが、ちょっと動作が怪しいかなぁ。日本語入力モードにすると、ATOKパネルが消えてしまう。俺の設定が悪いの?

 それに、キーバインドの変更に必要なカスタマイザがRosetta必須と来た。ジャストシステムのサイトを見ると、他にもいくつかRosettaが必要なツールがあるらしい。

 この辺、Rosettaをインストールすりゃ解決はするんだろうが、せっかくクリーンインストールしてるんだしRosettaインストールしないで解決したいなぁ。アップデートモジュール出してくれないだろうか>ジャストシステム様

 全体的に動作がシャキシャキ動くようになった気がするんだが、OSが新しくなったからなのかクリーンインストールだったのが功を奏しているのかは不明。

 ほんとはもっといろいろ比較したかったんだけど。。。。もうBoot Camp経由でVistaもセットアップしてしまったし、もう一度やる気にはならなかったorz

2009年6月 7日 (日)

USBドライブ上でアクセス制御

 マックユーザーのことをマカーって言うんだそうで。調べたらWikipediaにも載ってた・・・Wikipediaってホント何でも載ってるな。

 で、会社で俺が自宅のパソコンをMacに買い換えた話をしていたら、自分の席の後ろに座っている人が実はマカーだったことが判明。意外と近くにもいるもんだ。

 先週末のこと。その人と休み時間にMac話をしていて、「Macで自分専用のフォルダって作れないんですかねぇ」と。

 事情を聴いてみると、なんだか奥さんに見られたくないファイルがあるそうだ。まぁどんなファイルか敢えて聞かなかったにしても、

 男ならその気持ち、解ろうってもんだ!ヽ(`д´)ノ

 外付けのUSB HDD上にフォルダを作成していろんなファイルを突っ込んでいるそうなんだが、そのフォルダのアクセス権をどういじっても奥さん(同じMac上の別アカウント)から見えてしまうらしい。

 あれ。Macじゃできないんだっけか・・・? ってことでその宿題を俺が自宅に持ち帰って確認してみることに。

 ちなみに、以下すべてLeopardで確認した。

■状況確認

20090607account

 まず、アカウントを2つ作る。今回は「take」と「take2」。どちらも「ユーザにこのコンピュータの管理を許可」のチェックをしていない、通常のユーザだ。

 ターミナル上で、このアカウントの状況を「id」コマンドで見てみた。

$ id take
uid=502(take) gid=20(staff) groups=20(staff),102(com.apple.sharepoint.group.2),103(com.apple.sharepoint.group.3),101(com.apple.sharepoint.group.1)

$ id take2
uid=503(take2) gid=20(staff) groups=20(staff),102(com.apple.sharepoint.group.2),103(com.apple.sharepoint.group.3),101(com.apple.sharepoint.group.1)

 どちらのアカウントも、同じ4つのグループに属している。

  • staff
  • com.apple.sharepoint.group.1
  • com.apple.sharepoint.group.2
  • com.apple.sharepoint.group.3

 さてそれでは、アカウント「take」でUSB HDD上にフォルダを作成してみる。で、そのフォルダの情報を見てみると、

20090607folderinfo

 「共有とアクセス権」を見てみると、アカウント「take」には「読み/書き」ができる設定になっている。で、「staff」っていうのと「everyone」には「読み出しのみ」が設定されている。ってことは、この状態だと「staff」グループに属しているアカウント「take2」でも読み出しはできるってことになる。

 そりゃ奥さんに丸見えになるわな・・・

■アクセス制御でトライ

 ではここで、ファイルシステム上のアクセス権をいじってみる。「情報を見る」から「共有とアクセス権」の所を開くとできるはず。

20090607accessset

 ここで、グループ「staff」には「書き込みのみ」、「everyone」には「アクセス不可」を設定する。こうすれば少なくともアカウント「take」以外は見ることはできないはず。グループ「staff」には書き込めてはしまうが。

 で、アカウント「take2」でログインし直して同フォルダを開いてみると、

 なんと開けてしまうΣ('-'ノ)ノ

 ナンナンダ、、、

 アカウント「take」でターミナルを開いてUSB HDD上を見てみると、

$ cd "/Volumes/Mac USB Drive"/
$ ls -l
drwx-wx---  2 take  staff       68  6  7 00:56 testfolder

 うん。確かに自分以外は少なくとも読み込みはできない。

 じゃ、アクセスできてしまったアカウント「take2」でログインし直して同じく見てみると、

$ cd "/Volumes/Mac USB Drive"/
$ ls -l
drwx-wx---  2 take2  staff       68  6  7 00:56 testfolder

 なんと、ファイルのオーナーが変わってしまってる!

 これじゃどんなにがんばってアクセス制限しても、自分自身にアクセス許可をしている限り同じMac上にあるアカウントならアクセスできてしまうじゃないか。

 さて困った。

■ディスクイメージでパスワード保護

 ここでネットを探索。ディスクイメージを使ってパスワードをかければ、人に見られないようにすることができるらしい。

http://q.hatena.ne.jp/1200816163
http://technolo-walk.blogspot.com/2007/04/mac-os-x.html

 早速やってみる。まず「ディスクユーティリティ」を起動。ツールバーにある「新規イメージ」をクリックする。

20090607diskutility

 「ボリュームサイズ」はデフォルトで100MBとなっているが、格納するファイルの量にあわせて変えておいた方がいいだろう。

 ポイントは「暗号化」のところ。デフォルトは「なし」なので、ここを「128ビットAES暗号化」を選ぶ。

 これで、ファイル名を入力して作成する場所を指定してから「作成ボタン」をクリック。

 ちょっとするとパスワードの入力を求められるので、適当に入力。「OK」ボタンをクリックすると、見事ディスクイメージを作成することができた。ファイル名の拡張子は「.dmg」になっている。

 試しに別アカウントでこのファイルを開いてみると、パスワード入力を求められた。パスワードを正しく入力すると初めてマウントされ、デスクトップ上にもドライブの一つとして見えるようになる。

 よかった。この方法で解決できそうだ。週明けに報告してこよう。

2009年2月17日 (火)

【Cocoa】ドロア(NSDrawer)

 今回はドロアの使い方。NSDrawerを使う。参考にさせていただいたのはこちらの記事。

http://www.infoatmackers.jp/cocoa/programming/programming21.html

 最初ドロアを知らないときにInterfaceBuilderの中を覗いていてNSDrawerという文字を見て、「何か描画(Draw)する物?」とか勘違いしていた。辞書ひっぱってみたら「引き出し」って意味もあるんだな。NSDrawerは引き出しの方の意味。ウインドウの影からニョキッと出てくるパネルのような物だ。

 実際ドロアを使うだけなら、InterfaceBuilderだけでもできてしまうから驚き。

 ウインドウ上にボタンを1個用意して、そいつをクリックするとドロアが出たり入ったりするサンプルを作ってみることにする。

1.プロジェクトの作成

 いつものように「Cocoa Application」でプロジェクト作成。

2.InterfaceBuilderの起動

 「Resources」の「MainMenu.xib(English)」をダブルクリックしてInterfaceBuilderを起動する。

3.標準のウインドウを削除する

 「MainMenu.xib」ウインドウの中を見ると、メインウインドウをなる「Window(Window)」が入っているが、こいつを削除してしまう。マウスで選択しておいて「del」キーで消せる。

Drawer1

4.NSDrawerをMainMenu.xibに追加

 「Library」ウインドウの中の「Objects」の中を覗いて、「Cocoa」→「Application」→「Windows」と追っかけていくと、「Window and Drawer」というのが見つかる。こいつを「MeinMenu.xib」ウインドウにドラッグ&ドロップ。

Drawer2

 そうすると自動的に3つのオブジェクトが追加される。「Window(Window)」、「Drawer Content View」(なぜかドラッグ&ドロップ中はCustom Viewと表示される)、「Drawer」の3つだ。

 この3つ、実はそれぞれ一つずつLibraryから個別にドラッグ&ドロップして持ってこれるらしいのだが、そうした場合この3つを自力で接続してあげる必要があるらしい。「Window and Drawer」をドラッグ&ドロップしたときは、その辺を全部自動的にやってくれるらしい。

5.ドロアのデザイン

 ボタンをクリックすると顔を出すドロアの中身をデザインする。

 まず、「MainMenu.xib」の中の「Drawer Content View」をダブルクリック。すると、ドロアをデザインするためのウインドウが表示される。

Drawer3

 今回は難しいことをせず、文字だけ表示させておくことにする。ラベルを貼り付けて、適当な文字列を埋めておく。

Drawer4

6.ドロアの属性をいじる

 「MainMenu.xib」の中の「Drawer」をクリックして選択する。そうすると「Attributes」ウインドウで、「Edge」という属性をいじることができる。デフォルトでは「Right」が選択されている。これは、ドロアが顔を出す方向。「Right」ってことはウインドウの右側にニョキッとドロアが出てくる。ここで顔を出す方向を上下左右で選ぶことができる。

Drawer5

 あと「Size」ウインドウでドロアのサイズをいじれるんだが、とりあえず「Content」の「Width」だけ先程デザインしたドロアの幅に合わせておく。

Drawer8

 高さをいじってみたりしたんだが、実行しても値が反映されていないっぽい。使い方間違ってるんかな・・・。

7.メインウインドウ側のデザイン

 「MainMenu.xib」の「Window(Window)」をダブルクリックすると、メインウインドウのデザイン用ウインドウが表示される。こちらには、ドロアが表示されるきっかけのためのボタンを一つ用意する。

Drawer6

8.ボタンのアクション設定

 ボタンと、ボタンをクリックしたときのアクションメソッドを接続する。アクションメソッド自体は「Drawer」の中にもう用意されているので、自分で作成する必要は無い。

 ボタンから「Drawer」に向かってcontrol+ドラッグ&ドロップ。接続候補として「open」「close」「toggle」が表示されるが、今回は「toggle」を選ぶ。一回押す毎に出たり入ったりするアクションメソッドだ。

Drawer7

 試してないが、おそらく「open」はドロアを出す専用、「close」はドロアをしまう専用のメソッドなんだろう。

9.コンパイル&ゴー

 以上で作成は終了。Xcode側に戻って実行してみる。ボタンをクリックするたびにニョキニョキしてくれる。

Drawer9

Drawer10

10.あとは?

 ドロアの上にボタンなりなんなり好きな部品を配置して、いつものように自分で用意したコントローラクラスのアクションメソッドやアウトレットと接続してやれば、いろいろできるんだろう、きっと。まだ試してないけど。

11.ドロアの使われ方

 AppleのHuman Interface Guidelinesっていうのがあって、その中にドロアについて書いてあった。「PartIII:Aqua Interface」→「Windows」→「Window Elements」だ。

http://developer.apple.com/documentation/UserExperience/Conceptual/AppleHIGuidelines/XHIGIntro/chapter_1_section_1.html

 和訳するのが大変なので、和訳サイト様を頼ることに(´¬`;)

http://potting.syuriken.jp/potting_conv/XHIG_J/XHIGIntro/chapter_1.html

 それによると、

ドロワーは、かなり頻繁に利用される必要があるものの、常時見えている必要はないというコントロールのためだけに使用してください

シートは第一に、「シートを使用するとき」(209ページ)で説明されているとおり、モード式ダイアログを置き換えることを意図していますが、ドロワーは付加機能を提供することを意図しています。

シートが開いたとき、それがウインドウの中心となり、ウインドウの内容を覆い隠します。ドロワーが開いたとき、親ウインドウのすべては今までどおり見えており、利用できます。

 などと言ったことが書かれていた。

 ダイアログやシートと違って、ドロアの場合だと親となるウインドウは常時見えていて、かつ操作も可能な状態だから、ダイアログやシート的な使い方はよろしくないってことなのかな・・・。

 確かにダイアログ的な使い方をするならダイアログ使えばいいしな。隠すことができるツールバーって言うかツールパネルみたいな感覚なんだろうか。

2009年2月15日 (日)

【Cocoa】リストビューって無い?

 VCLで言うところのTListBoxクラスとか.NET Frameworkで言うところのListBoxクラスとかに当たるクラスがCocoaに見当たらない。

 見た感じはテーブルビューの1列固定のやつで、ヘッダが無くて縦スクロールバーはあるんだけど横スクロールバーが無いやつ。

 探し足りないだけなのなら自分を恨んで終わりにするんだが・・・本当に無いんだろうか。

 システム内で使われてるリストボックス的な部品って無かったっけか・・・。システム環境設定の中の「ディスプレイ」タブ内の「解像度」の一覧が近いかな。

Listview

 システム環境設定のダイアログと、解像度のところをNSTableViewを使って似せて作った自作のアプリ(機能ゼロだけど)を並べてみたけど・・・・変わんないねこれ。

 結論。

 Cocoaでは、リストビューっぽいのを使いたければNSTableViewを使えば事足りる。

2009年2月14日 (土)

【Cocoa】アプリケーションの初期化

 前回NSTableViewの勉強をしていたときに、つまずいた話を一つ。

 結局前回のサンプルでは、NSTableViewから問い合わせがあった時に固定の文字列を返す物にしてしまった。

 が、実は最初のうちはNSArrayを使って文字列の配列を用意してそれをテーブルに表示する物を作成していた。ところが、NSArrayの作成に問題は無いのにテーブルには何も表示されないという現象にぶち当たってしまったのだ。

 いろいろ試しているうちに、ボタンを用意してそのアクションメソッド内から「NSTabeleView」の「reloadDataメソッド」(テーブルに表示するデータをリロードするメソッド)を呼び出すと、ちゃんと表示してくれるところまではたどり着いた。

 ってことは、NSArrayを作るタイミングが遅すぎる?

 この時、NSArrayの作成はApplicationからデリゲートして「applicationDidFinishLaunching」の中でやっていた。

 でも、このデリゲートメソッドでタイミングが遅いってんなら、どこでやるのがいい?

 そこで参考にしたのがこちらの記事。

「Cocoa探検隊」さんの探検レポート「awakeFromNibの落とし穴」
http://www013.upp.so-net.ne.jp/tanken/Tanken/no4_PitfallOfNib.html

 ここの記事は、今回俺が悩んでいることとちょっと趣旨が違うかもしれないのだが、ここにアプリケーションが起動するときに送られるメッセージの順番が載ってた。これは参考になりそう。

 で、この記事を手がかりにしつつ、今回自分が作ったサンプルでどのように動いているのか試してみた。

 アプリケーションが起動するときに呼ばれそうなデリゲートメソッド内でNSLog()を呼び出してみたところ、だいたい次の順番っぽい。

  • initメソッド(今回はここで文字列の準備もした)
  • numberOfRowsInTableViewメソッド(テーブルの行数の問い合わせ)
  • awakeFromNibメソッド
  • numberOfRowsInTableViewメソッド(テーブルの行数の問い合わせ)
  • tableviewメソッド(テーブル上に表示する値の問い合わせ、表示は2*4で8回)
  • applicationWillFinishLaunchingメソッド
  • applicationWillUpdateメソッド
  • applicationDidUpdateメソッド
  • applicationDidFinishLaunchingメソッド
  • applicationWillUpdateメソッド
  • applicationDidUpdateメソッド
  • tableviewメソッド(テーブル上に表示する値の問い合わせ、表示は2*4で8回)
  • applicationWillBecomeActiveメソッド
  • applicationDidBecomeActiveメソッド
  • applicationWillUpdateメソッド
  • applicationDidUpdateメソッド

 テーブルビューの行数の問い合わせが2回も発生していたり、一度表示する値を一通り問い合わせたかと思ったら再び画面のアップデートが発生してもう一度表示する値をなめ直したりと、素人目には不可解なところもあったりなかったり。

 それでも、最初俺が「applicationDidFinishLaunching」で文字列を作成していたタイミングでは、既に行数の確認は終わっていて明らかに遅そうだ。

 逆に、「init」で文字列を準備できれば確実に表示できることは確認できた。

 ちなみに、「awakeFromNib」内で文字列の用意をしてみたのだが、これだと表示域が4行でデータ行数が7行であるにもかかわらず縦スクロールバーが表示されない物ができてしまった。

 ここまで来てやっと気がついたこと。それは「文字列作ったら自分でリロードしてやればいいのか・・・」。

 ってことで、「applicationDidFinishLaunching」の中で文字列の作成→テーブルビューのreloadDataメソッドの呼び出しで、やっと期待通りに動作してくれた。

 我ながら遠回りしすぎてる気がしなくもない(´・ω・`)

【AppController.h】

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
{
    IBOutlet id _tableView;
    NSArray* ar;
}
- (id) init;
- (void)awakeFromNib;

//Applicationデリゲート
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
- (void)applicationWillUpdate:(NSNotification *)aNotification;
- (void)applicationDidUpdate:(NSNotification *)aNotification;
- (void)applicationWillBecomeActive:(NSNotification *)aNotification;
- (void)applicationDidBecomeActive:(NSNotification *)aNotification;

//アクションメソッド
- (IBAction)btnReload:(id)sender;

//データソース
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView;
- (id)tableView:(NSTableView *)aTableView
    objectValueForTableColumn:(NSTableColumn *)aTableColumn
    row:(NSInteger)rowIndex;
@end

【AppController.m】

#import "AppController.h"

@implementation AppController
//---------------------------------------------------------
- (void) makeArray
{//テーブルに表示する文字列達の作成
    ar = [[NSArray arrayWithObjects:
        @"Sunday",@"Monday",@"Tuesday",@"Wednesday",
        @"Thursday",@"Friday",@"Saturday",nil] retain];
}
//---------------------------------------------------------
- (id) init
{
    [super init];
//    [self makeArray];
    NSLog(@"init completed.");
    return self;
}
//---------------------------------------------------------
- (void)awakeFromNib
{
//  [self makeArray];
//  [_tableView reloadData];
    NSLog(@"awakeFromNib completed.");
}
//---------------------------------------------------------
//アクションメソッド
//---------------------------------------------------------
- (IBAction)btnReload:(id)sender
{//テーブルビューのリロード
    [_tableView reloadData];
    NSLog(@"btnReload completed.");
}
//---------------------------------------------------------
//Applicationデリゲート
//---------------------------------------------------------
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
    NSLog(@"applicationWillFinishLaunching completed.");
}
//---------------------------------------------------------
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    [self makeArray];
    [_tableView reloadData];
    NSLog(@"applicationDidFinishLaunching completed.");
}
//---------------------------------------------------------
- (void)applicationWillUpdate:(NSNotification *)aNotification
{
    NSLog(@"applicationWillUpdate completed.");
}
//---------------------------------------------------------
- (void)applicationDidUpdate:(NSNotification *)aNotification
{
    NSLog(@"applicationDidUpdate completed.");
}
//---------------------------------------------------------
- (void)applicationWillBecomeActive:(NSNotification *)aNotification
{
    NSLog(@"applicationWillBecomeActive completed.");
}
//---------------------------------------------------------
- (void)applicationDidBecomeActive:(NSNotification *)aNotification
{
    NSLog(@"applicationDidBecomeActive completed.");
}
//---------------------------------------------------------
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
    [ar release];
}
//---------------------------------------------------------
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
{
    return YES;
}
//---------------------------------------------------------
//データソース
//---------------------------------------------------------
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{//データの行数問い合わせに対する返答
    NSLog(@"numberOfRowsInTableView completed.");
    return [ar count];
}
//---------------------------------------------------------
- (id)tableView:(NSTableView *)aTableView
    objectValueForTableColumn:(NSTableColumn *)aTableColumn
    row:(NSInteger)rowIndex
{//
    NSLog(@"tableView completed.");
    if([[aTableColumn identifier] isEqual:@"col1"]){
        //1列目は行番号
        return [NSNumber numberWithInt:rowIndex];
    }
    else if([[aTableColumn identifier] isEqual:@"col2"]){
        //2列目は行番号に対応する文字列
        return [ar objectAtIndex:rowIndex];
    }
    else{
        //ここは処理しないはず
        return nil;
    }
}
//---------------------------------------------------------
@end

Startup1

2009年2月11日 (水)

【Cocoa】NSTableViewのお勉強

 バインディングの記事を書きたかったが、あまりに俺の頭がついて行けてないので、いったん保留することに決定。煮詰まりそうだ・・・難しく考えすぎてる?

 それで、今度はテーブルビューの使い方を勉強した。クラス名は「NSTableView」。BorlandのVCLだと「TStringGrid」に相当する部品。行と列があって、それぞれのマス目に文字列を表示できる。

 勉強のネタ本にしている「たのしいCocoaプログラミング」には、「文字列以外に画像やボタンなども入れることができる」と書いてある。スゲー。

■NSTableViewの基本

 使い方というかテーブル表示の実現の仕方が、VCLのTStringGridとはまるで異なっているようだ。

 まず表示するデータはNSTableViewのオブジェクトに突っ込んでいくようなことはしないらしい。あくまでNSTableViewは見た目の表示部分だけを司り、表示内容となるデータは別個用意する必要があり、そのデータ形式はどんなんでもいいらしい。

 ではNSTableViewは、別個に用意された形式不明のデータをどうやって表示するのかというと、NSTaableViewから来る(最低限)二つの問い合わせに答えるプログラムを用意することで実現しているらしいのだ。

 二つの問い合わせとは「データは何行あるのか」と「X列Y行のデータは何か」というもの。

 たとえ1万行あるデータであったとしても、実際にウインドウ上に5行しか表示しないにであれば、この問い合わせは5回しか発生せず、スクロールして新たな行を表示する必要が出た時点で、その分だけしか問い合わせされないというのだ。

 その二つの問い合わせとは、以下の二つのメソッドになる。

- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
- (id)tableView:(NSTableView *)aTableView
    objectValueForTableColumn:(NSTableColumn *)aTableColumn
    row:(NSInteger)rowIndex

 この二つは「NSTableDataSource」プロトコルに定義されているメソッドで、他にも「ソートの昇順降順が入れ替わりました」とか「X列Y行にデータが書かれました」とかいったメソッドがある。

 これらのメソッドは「クラス」ではなく「プロトコル」として定義されているので、このメソッドを使用するために特定のクラスを継承したりする必要はなく、何かしらのオブジェクトにこの名前のメソッドを実装するだけでいいらしい。それがプロトコルという物だそうだ。

■実践

 簡単な例で使用方法を確認してみた。

 目指すところは、2列7行のテーブル表示。1列目は0〜6までの数字を表示する。2列目には「Sunday」〜「Saturday」までの7つの曜日を表示する。

1.プロジェクトの生成

 いつものようにXcodeを起動。新規プロジェクトで「Cocoa Application」を選択。プロジェクト名は「TableView」にした。

2.InterfaceBuilderを起動

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

3.コントローラオブジェクトの用意

 まずはコントローラオブジェクトを用意する。Libraryから「NSObject」を「MainMenu.xib」ウインドウにドラッグ&ドロップ。Identityウインドウでクラス名として「AppController」と命名しておく。

Tableview1

 今回はアクションもアウトレットも使わないので、このままクラスファイルとして出力してしまう。MainMenu.xibウインドウの 「AppController」を選択した状態で、メニューの「File」→「Write Class Files ...」をクリック。ファイルを保存する。ファイル名は「AppController.m」ヘッダファイルもチェックを入れておく。

4.ウインドウにテーブルビューを配置

 続いてウインドウ上にテーブルビューを配置する。部品自体はLibraryの「Cocoa」→「Views&Cells」→「Data Views」の中にあるので、そいつをウインドウにドラッグ&ドロップ。

Tableview2

5.データソースの接続

 今回のプロジェクトでデータソースの役をするのは、先程作成した「AppController」ということにする。つまり、テーブルビューはAppControllerに対して「何行あるのか」とか「X列Y行のデータは何か」という問い合わせをするように仕向ける。

 やり方はアクションなどを接続するときと同様。ウインドウ上のテーブルビューを選択した状態で、「control+ドラッグ」で「AppController」にドロップし、表示される接続候補から「dataSource」を選ぶ。

Tableview3

 このとき少しコツがいる。

 最初に選択するテーブルビュー側なんだが、部品を一回クリックするだけでは上記のような操作はできない。Identityウインドウのクラス名のところを見ると「NSScrollView」が選択されてしまっているのだ。

Tableview4

 テーブルビューが選択されている状態で、もう一度テーブルビューをクリックすると、「NSTableView」をうまく選択できる。この状態でないといけない。

 だからといって、テーブルビューをダブルクリックすると、「NSTableColumn」が選択されてしまう。ダブルクリックをするのではなくクリックを2回、だ。

Tableview5

6.列に識別子を付ける

 これは、実際に「X列Y行のデータは何か」の問い合わせが合った際に、行は整数型で問い合わせが来るんだけど、列の方はここで設定する識別子で来ることになるらしいので、ここで設定する。

 今度はテーブルビューをダブルクリックして「NSTableColumn」を選択する。テーブルに列は2つあるはずなので、それぞれの列をダブルクリックで選択して、識別しを設定する。

 設定する箇所は、「Attributes」ウインドウの「Identifier」。

Tableview6

 ここでは単純に「col1」、「col2」としておいた。

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

7.ヘッダファイルの記述

 Xcodeに戻って、「AppController.h」にいくつか記述を追加する。一つは親クラス。いつものように「NSObject」にする。もう一つは、テーブルビューからの問い合わせに対する応答をするための二つのメソッド宣言の追加。

【AppController.h】

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject{
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView;
- (id)tableView:(NSTableView *)aTableView
    objectValueForTableColumn:(NSTableColumn *)aTableColumn
    row:(NSInteger)rowIndex;
@end

8.メソッドの実装その1(データは何行あるのか)

 次は「AppController.m」の記述。

 まず「データは何行あるか」の問い合わせに応答する「numberOfRowsInTableView」メソッドだ。今回は7行固定なので、

- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
    return 7;
}

 とした。これが例えば配列クラスをデータソースにしていて、データ数も可変な場合は、NSArrayクラスのcountメソッドをreturnしてあげるなどで応用できる。

9.メソッドの実装その2(X列Y行のデータは何か)

 次に、「X列Y行のデータは何か」に応答する「tableView:objectValueForTableColumn:row:」メソッドだ。

 行の方はrowで指定されるNSInteger型の変数に格納されてくるので、そのまま使えそう。

 列の方はobjectValueForTableColumnで指定される変数を使うのだが、こちらは「NSTableColumn」オブジェクトへのポインタとなっている。要は列を表すオブジェクトだな、これ。さっきテーブルビューをダブルクリックして選択したやつだ。

 この列のオブジェクトから識別子を取り出すには、「identifier」メソッドを使用する。さらに、例えばその列の識別子が「col1」と等しいかどうかを判断するには、「isEqual」メソッドを使って「@"col1"」と比較すればいい。

 1列目なら、rowIndexで渡された0〜6までの数値を文字列にして返す。

 2列目なら、rowIndexで渡された0〜6に対応する曜日の文字列を返す。

 というわけで、ソースはこんな風になった。

【AppController.m】

#import "AppController.h"

@implementation AppController
//-------------------------------------------------------
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
    return 7;
}
//-------------------------------------------------------
- (id)tableView:(NSTableView *)aTableView
    objectValueForTableColumn:(NSTableColumn *)aTableColumn
    row:(NSInteger)rowIndex
{
    if([[aTableColumn identifier] isEqual:@"col1"]){
        //1列目の場合
        //rowIndexをそのまま文字列にして返す
        return [NSString stringWithFormat:@"%d",rowIndex];
    }
    else if([[aTableColumn identifier] isEqual:@"col2"]){
        //2列目の場合
        //0:Sunday 1:Monday.....を返す
        switch(rowIndex){
            case 0:
                return @"Sunday";break;
            case 1:
                return @"Monday";break;
            case 2:
                return @"Tuesday";break;
            case 3:
                return @"Wednesday";break;
            case 4:
                return @"Thursday";break;
            case 5:
                return @"Friday";break;
            case 6:
                return @"Saturday";break;
            default:
                return @"unknown";break;
        }
    }
    else{
        //それ以外の場合(ここは処理しないはず)
        return @"";
    }
}
//-------------------------------------------------------
@end

 できあがりはこんな感じ。

Tableview7

2009年2月 7日 (土)

Win→Mac移行作業、その後

 Cocoaの方はバインディングで行き詰まってしまったので、気分転換。

 Macを使い始めてほぼ1ヶ月が経過した。今のところいたって快適だ。操作感がとても心地いい。Macを購入する前に抱いていた不安はどこへやら、である。

 とはいえ、やはり一部どうしてもMacでは解決できない部分もあったりする。特にSony関係(パソリとかWalkman関連とか)。そんなときはBootCampやVMWare Fusionが強い味方だ。こいつがいるおかげで安心できる部分もあるのかもしれない。

 それでも、事実BootCampやVMWare Fusionを起動する機会も徐々に減ってきている。こんな俺でもMac色に染まりつつあるようだと思える。

 使っているうちに使い方などについての勘違いや、当初困っていた操作方法の解決案も、俺なりにいくつか見いだすことができた。

■マウスの中クリックについて

 FireFoxで、リンクに対するマウスの中クリックを新規タブに割り当てたいのに、どうしてもExposeが起動してしまって希望の動作にできなかったことを書いたが、この原因がわかった。中クリックでExposeが起動しないようにすることもできた。

 そもそも、iMacにはMighty Mouseというのが標準添付されている。が、Windowsで使用していたUSBマウスでも普通に接続して使用することができる。このMighty Mouseを接続しているときとその他のマウスを接続しているときとで、環境設定でのマウスの設定項目に差があることに後から気がついた。

 まずこれがMighty Mouseを接続しているときのマウスの設定画面。

Mouseconfig1

 左右ボタン、真ん中のトラックボール、マウスの横についてるボタンのそれぞれをカスタマイズすることができる。標準では、トラックボールのクリック操作でExposeが起動される設定になっていた。

 次にこれがWindowsで使っていたUSBマウスを接続したときのマウスの設定画面。ちなみに接続していたのは2ボタン+ホイールの物。

Mouseconfig2

 ボタンをクリックしてExposeの起動を設定するようなところが無くなっている。

 で。

 最初Mighty Mouseを接続してトラックボールクリックにExposeを割り当てている状態でWindowsUSBマウスに切り替えると、設定項目は無いのだがホイールクリックでExposeが起動してしまっていたのだ。

 いったんMighty Mouseに接続しなおしてトラックボールに「切」を設定して、その後WindowsUSBマウスに切り替えたら、何の問題も無く中クリックが使えるようになった。

 これを、何も知らないうちにWindowsUSBマウスに付け替えてしまってしまったために、変なところで悩んでしまった。これで無事解決。後で以前の記事も直しておく。

■メールの振り分けタイミング

 メールクライアントにはThunderbirdを使用している。Windowsでは秀丸メールを使用していた。

 秀丸メールを使用していたときは一度受信したメールは振り分けせずに置いておいて、一通り読み終わった後に一括振り分けをしていた。場合によっては一通り読み終わる前にメールクライアントを一回閉じて別の作業をしたり、次の受信をして急ぎのメールを先に読んだりすることもあって、ちゃんと読み終わるまでは振り分けしたくない気持ちがあったからだ。

 ところがThunderbirdでは受信、即振り分けという動作をしており、この動作は標準ではどうにもならなそうだ。

 Thunderbirdのツールバーには「前へ」「次へ」と言うボタンがある。このボタンで未読のメールだけを追いかけることが可能だ。で、これは後から気がついたのだが、「進む」「戻る」というボタンもある。これは自分が見たメールの参照履歴を追いかけてくれる。一度見て参照済みにしてしまっても「戻る」ボタンでもう一度見れることに気がついた。

Thunderbird1

 今までボタンを使って参照することをしてこなかった俺には、このボタンの存在に気がつくことができなかった。人によっては「そんな当たり前なことを」と思うのかもしれないが・・・。

 とにかくこれで俺も、まだ見ていないメールが振り分け先のフォルダに移動してしまっていても、割と簡単に追いかけることができるようになった。そんなわけで今は、この4つのボタンで事なきを得ている。

 あともう一つ。名前は失念してしまったが一括振り分けを後から自分のタイミングで行うThunderbird用のプラグインを見つけたのだが、使い方に癖があり俺には合わないかと思い、使用をあきらめた。

■イメージビュアー

 Windowsで使用していたIrfanViewに代わる物をさがしていたのだが、iMacを購入したときに一緒に購入したPhotoshop ElementsにBridgeというソフトが付いていて、こいつがかなり俺の希望を満たしてくれることに後から気がついた。今はこちらを使用している。

■アプリランチャー

 Windowsの時はKick Inというソフトを使用していた。できるだけマウスの移動距離が短くて、アプリのグループ化ができて、管理が簡単なやつがいいと思い探していたが、結局今は「FinderPop」を使用している。

 こいつは、デスクトップを右クリックするとポップアップメニューが出てくるが、その中に設定したアプリのメニューが追加されるというもの。

Finderpop1

 アプリのショートカットを作って特定のディレクトリに放り込む作業が面倒ではあるが、一度設定してしまえば非常に使い心地はいい。

2009年2月 5日 (木)

【Cocoa】アプリケーション設定の保存と読み込み

 アプリケーション設定の保存と読み込みについて勉強してみた。

 Windowsアプリであればレジストリを使ったりiniファイルを使ったりして設定を保存するのが一般的だと思う。ファイルとして保存するなら、場所は「Documents and Settings」配下とかインストール先の「Programs Files」配下とか?

 Mac(OS X)の場合を調べて見ると、「/Users/ユーザ名/Library」配下にファイルを作って保存するのが一般的らしい。Finderでのぞいてみると、ユーザホーム内にある「ライブラリ」というディレクトリがそれだ。特にCocoaアプリであれば、「NSUserDefaults」というクラスを使うと、この辺に保存と読み込みをしてくれるらしい。

 このNSUserDefaultsを調べ始めると、NSDictionaryを使ったデフォルトの設定だとかバインディングといった話が出てきて収集がつかなくなってきたので、とりあえず簡単なところから確認しつつまとめてみる。

■NSUserDefaultsを利用したときの保存のされ方

 「/Users/ユーザ名/Library/Preferences」ディレクトリ内に、プロパティリストという形式のファイルで保存される。ファイル名の拡張子は「.plist」。

 Xcodeをインストール済みの場合はProperty List Editorというのがインストールされるらしく、「.plist」のファイルを開こうとするとこのツールが起動して内容を参照・編集することができるようだ。

 ファイル名の方は、Xcodeの方で「Resources」の下に「Info.plist」というのがあって、この中の「Bundle Identifier」の設定値が使われる。ここに設定される値は逆DNS表記っていうらしく、urlを逆から書いていって最後にアプリの名前をくっつける書き方。こうすることで一意性を保つらしい。

Savesettings1

 デフォルトでは「com.yourcompany.${PRODUCT_NAME:identifier}」と設定されているのだが、俺の場合はこのブログのurlを使って、「com.cocolog-nifty.take-blizzard.${PRODUCT_NAME:identifier}」としてみた。${PRODUCT_NAME:identifier}はプロジェクトの名前だろうか。

 今回プロジェクトの名前は「SaveSettings」としているので、最終的に保存されるplistファイル名は「com.cocolog-nifty.take-blizzard.SaveSettings.plist」ということになる。

■NSUserDefaultsでplistファイルを読む

 コーディングは次のようになる。

    NSUserDefaults* ud = [NSUserDefaults standardUserDefaults];

 これだけで、今回の場合は「/Users/ユーザ名/Library/Preferences/」にあるplistファイル「com.cocolog-nifty.take-blizzard.SaveSettings.plist」を読み込んでくれた。

■値を取り出す方法

 NSUserDefaultsオブジェクトの中(plistファイルの中もそうだけど)には、「キー」と「値」の組み合わせで格納されている。この辺Windowsのレジストリやiniファイルと同じ感じ。なので、値を取り出すときはキーを指定することになる。

 例えばint型の値を取り出すときは、

    int i = [ud integerForKey:@"キー名"];
    //udはNSUserDefaultsのオブジェクト

 で取り出せる。int型以外いろいろな型の取り出しメソッドが用意されている。

■値を変更する方法

 値を変更するには「どのキー」を「どういう値」に変更するかを言うので、変更の際には「キー」と「値」を指定する。

 例えばint型の値を変更するには、

    int i = 0;
    [ud setInteger:i forKey:@"キー名"];
    // udはNSUserDefaultsのオブジェクト

 とすると、NSUserDefaultsオブジェクト内の値を変更できる。

■plistファイルに出力する方法

 set〜:forKey:メソッドで値をいくら書き換えても、それはメモリ上の値の操作に過ぎない。その結果をplistファイルに出力するには「synchronize」メソッドを使用する。

    [ud synchronize];
    // udはNSUserDefaultsのオブジェクト

■実際のコード

 今回読み込み保存の対象にしたのは、ウインドウの位置とサイズ。アプリ起動時に設定を読み込んでウインドウに反映し、終了時に保存する。

 アプリの起動のタイミングはNSApplicationクラスをデリゲートして、「applicationWillFinishLaunching」というメソッドでつかまえる。終了のタイミングは同じくNSApplicationクラスをデリゲートして「applicationWillTerminate」メソッドでつかまえる。ウインドウの位置とサイズにアクセスするため、NSWindowクラス用のアウトレットを用意している。

【AppController.h】

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject{
    IBOutlet id mainWindow;
}
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification;
- (void)applicationWillTerminate:(NSNotification *)aNotification;
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:
        (NSApplication *)theApplication;
@end

【AppController.m】

#import "AppController.h"
@implementation AppController

//-----------------------------------------------------------
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{//アプリケーション開始
    NSUserDefaults* ud = [NSUserDefaults standardUserDefaults];
    NSRect rect;

    rect.origin.x = [ud integerForKey:@"x"];
    rect.origin.y = [ud integerForKey:@"y"];
    rect.size.height = [ud integerForKey:@"h"];
    rect.size.width = [ud integerForKey:@"w"];

    //念のため4つとも読み出しができた時だけウインドウに反映する。
    if((rect.origin.x!=0)&&(rect.origin.y!=0)&&
        (rect.size.height!=0)&&(rect.size.width!=0)){
        [mainWindow setFrame:rect display:YES];
    }
}
//-----------------------------------------------------------
- (void)applicationWillTerminate:(NSNotification *)aNotification
{//アプリケーション終了
    NSUserDefaults* ud = [NSUserDefaults standardUserDefaults];
    NSRect rect = [mainWindow frame];
    [ud setInteger:rect.origin.x forKey:@"x"];
    [ud setInteger:rect.origin.y forKey:@"y"];
    [ud setInteger:rect.size.height forKey:@"h"];
    [ud setInteger:rect.size.width forKey:@"w"];
    [ud synchronize];
}
//-----------------------------------------------------------
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:
    (NSApplication *)theApplication
{//窓閉じ即終了
    return YES;
}
//-----------------------------------------------------------
@end

 で、このアプリを起動して終了すると、plistファイルが出力されていることが確認できた。

Savesettings2

 調べている最中だが、どうもバインディングというのを使うともっと簡単に設定の保存読み出しができるようだ。ある程度まとまったら、また記事にする予定。

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だと左上基準なんだけど。

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日 (水)

【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というキーワードを使って定義する。先頭にハイフン必須。
- (戻り型)メソッド(引数型)引数

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月30日 (火)

Cocoaプログラミングの入り口を模索中

 Mac上でプログラミングしてみたくて、Macに関して全く知識がない状態でまずはネット検索から始めてみた。以下、用語のまとめ。もしかして俺の理解不足で、正しくないことを書いているかもしれないので注意。


■Cocoa

 いわゆるフレームワークらしい。Windowsで言えばMFCとか.NET Frameworkに相当するものか。

■XcodeとInterfaceBuilder

 Xcodeは統合開発環境。InterfaceBuilderはGUI設計ツール。プログラミングをする際は、まずXcodeを起動してプロジェクトを作成し、インターフェースを作る段階でXcodeからInterfaceBuilderが呼び出される感じ。

 ちなみに、俺のiMacにインストールされているXcodeのバージョンは3.0だった。


 で、またさらにいろいろ探してたら、こんなサイトを見つけた。

Cocoaはじめの一歩(Spread Your Wings!内)

 これを読み進めていくと、Become An Xcoderという入門ガイドの日本語版を見つけることができる。これはいいなぁと思い早速読んでみた。

 前半はほぼObjective-Cに関する記述だが、内容的にはC言語の基本部分の説明なので、この辺なら俺でも理解はできた。

 途中からXcodeやInterfaceBuilderを使っての説明が増えていくのだが、その辺でちょっと違和感が出てきた。解説されているような機能や操作ができないのだ。最初は俺が探し足りないだけかと思っていたらどうもそうではないらしく、どうやら俺が使っているXcodeのバージョン3.0には対応していないドキュメントだったらしい。

 それでも考え方を学ぶのにはちょうどいいだろうと言うことでとりあえず斜め読み。

 さらにネット上を探してみるとこちらのブログにXcode 3.0のチュートリアルムービーが存在しているという記事を発見。

 ムービーの音声自体は英語でしゃべってはいるが、だいたい何となく操作している内容だけでも参考になった。しばらくこれをとっかかりにして勉強する予定。

2008年12月26日 (金)

Win→Mac移行中 HTMLエディタ編

 Win→Mac移行計画進行中。今回からブログのカテゴリーに「Mac」を追加してみた。

 前回Mac用のHTMLエディタについて、hpDrafterを 試す予定と書いた。が、実際いじってみると、自分的要件だったWYSIWYG編集ができなかったので保留にすることに。んー残念。

 で、さらにネットを検索。KompoZerと いうソフトを見つけた。

 調べてみるとこのソフト、Mozillaの流れを組むソフトらしい。以前Mozilla Application SuiteのなかにFireFox、ThunderbirdとともにComposerというHTMLエディタが含まれていたが、その後それが独立して Nvuというソフトになり、さらにその後釜として現在も開発が続いているのが、このKompoZerという話のようだ。

 使ってみると、思った以上に俺が欲しかった機能が網羅されていることがわかった。

  • WYSIWYG編集ができる。
  • HTMLソースの編集もできる。
  • HTML4.0、XHTML1.0/1.1にも対応できている。
  • SJIS、EUC、UTF-8で読み出し、保存ができる。
  • CSSエディタがついてる。覚えてなくても作成してくれる。

 ここまでいろいろ揃っていると、もう少し欲が出てきた。

  • 文章を選択してキーボードから1ストロークでパラグラフ化できない。

 これ、ホームページビルダーだったらCtrl-Pを入力することでパラグラフ化してくれる(つまりPタグで囲ってくれる)。 KompoZerでは、文章を選択した後ツールバーにあるドロップダウンから「段落」を選んでパラグラフ化する。一度キーボードから手は離れるのが面倒だが、自分でPタグを埋めるよりはマシなので良しとする。

 このソフト、Mac版の他にLinux版やWindows版も存在する。いっそのこと仕事先のWindowsマシンの方もホームページビルダーから 乗り換えてしまおうか‥‥‥そんな気にもなってきた。

 とりあえず、HTMLエディタについてはめでたく落ち着き先が見つかった。今回の記事も、miで下書きしてKompoZerで清書してアップしている。ソフトの作者様方々には本当に感謝感謝だ。

 徐々にではあるが、Macでも満足のいく環境ができつつある。探せばどうにかなるもんだ。