完全なC互換。CocoaはObjective-CのAPI。Cocoaについて学べば必然的にObjective-Cについて学べます。主な開発環境はXcode(または旧Project Builder)。プログラミング言語
一般的な Object 指向の思想を理解している前提の元に文法事項を解説。全てをつぶさに読む必要はない。Cocoa の方をやりながら文法事項を知りたいなと感じれば、外国語の学習同様に、必要な時に必要な所を参照した方がよい。
Objective-C では新たに次の様な型が定義されている(objc-class.h)。
言語構造上の変数は
など馴染みの物がある。これらは一例に過ぎないし全てを使うわけではないから、追々説明する事になるだろう。
定義は.hファイル、実装は.mファイルに書く。Xcode で新たに書類をプロジェクト追加する際に、Objective-C Class テンプレートを選ぶと、.hファイルと.mファイルが自然に追加される。
// MyObject.h
@interface MyObject : NSObject{
}
@end
// MyObject.m
@implementation MyObject
@end
Objective-C では C と区別(或いは将来の互換性確保)の為に、拡張した文法は@を付け記述する(ディレクティブ参照)。今この記述では、NSObject を継承した MyObject クラスの定義をした事になる。
註:Objective-C において、一般に新たなクラスを定義する場合は NSObject を継承して定義する。その理由は NSObject が Cocoa (及び Objective-C)における一般的な手続きを全て網羅しているからである。またその他のクラスに於ては、特段の理由や意味がない限り、サブクラスを造らない方が良い(例えば NSString や NSArray のサブクラスを作製しても思う様にはいかないだろう)。逆に能くサブクラスを作るのは、NSObject の他に、稀だが NSApplication の場合や、或いはサブクラスを作る為のクラス NSManagedObject 等である。
目的別に使いわけることになる。Cocoa の設計思想上、尤もよく使用されるサブクラスは、Controller である。つまり、Model と View に既存の部品を用いる事で、生産性を向上させているが、勿論、譬えば NSButton 等のサブクラスを作ることで View を記述する事もある。
クラスは大文字で始めねばならない。Objective-C は 大文字と小文字を峻別する。今幾つかクラス名を挙げてきたが、自身のクラスを作る際に NS を接頭辞としてはならない。NS は NeXTSTEP の略であり、今では Apple による API を区別する為のクラス名接頭辞として予約されている。注意して欲しい。他にも IO(Input-Output)や OS(Operating System) や CG(Core Graphics)等 API 系により、ある程度の先客の規約があるので、紛らわしい名前は避けた方がよい。
今先程のクラスに、インスタンス変数 obj、クラスメソッド myClassMethod、メソッド myMethod を追加したとすると、
// MyObject.h
@interface MyObject : NSObject{
id obj;
}
+(void)myClassMethod;
-(id)myMethod;
@end
// MyObject.m
@implementation MyObject
+(void)myClassMethod
{
/// your code
}
-(id)myMethod
{
return obj;
}
@end
ヘッダにおける括弧の使い方に注目して欲しい。その括弧は、インスタンスが保持する変数を記述する箇所で、メソッドを記述する箇所ではない。メソッドは別に定義する。尚クラスメソッドは+、インスタンスメソッドは-で書き始める規則がある。以後メソッド名と共にクラスメソッドかインスタンスメソッドかを示す際には ± を以て明示する事にする。
さてメソッドは、@ の囲み、@interface ~ @end と @implementation ~ @end に於て記述する。文字通り定義と実装を表す。なお Objective-C は完全な Object 指向を目指す物ではなく実用を任とする言語であり、Ruby の Kernel Object 様な物は無く、つまり通常の関数は C と同じ方法で定義し利用できる。だから囲みの外で関数を定義するとよい(註:C のコードとは共存できる)。
今、更にこの MyObject のインスタンスを保持した変数 receiver が存在したとすれば、二つのメソッドを呼出すには、
[MyObject myClassMethod];
[receiver myMethod];
と書く(結果を取得するなど変数の扱いは C と全く同じなので略)。Objective-C に於てインスタンスをレシーバと呼ぶが、その意味は、Objective-C に於けるメソッドの呼出され方・動作に由来する。一般に「レシーバにメッセージを送る」と言えば、インスタンスメソッドを起動する事を意味する。
註:id型はObjective-Cに於て新たに定義された型の一つで、任意のレシーバを表す型である。この様な汎用的な型については附録参照。
引数を渡すには幾つか規則が存るが、まず単純な一つ渡す方法を示す。今 id 型の変数 value を -myMethod に渡すとして、
[receiver myMethod:value];
と書く。メソッド名の後に :(コロン)を打って引数を書くと、レシーバにメッセージが送信される。上の定義と実装を書き直せば、
// MyObject.h
@interface MyObject : NSObject{
id obj;
}
+(void)myClassMethod;
−(id)myMethod:(id)val;
@end
// MyObject.m
@implementation MyObject
+(void)myClassMethod
{
/// your code
}
−(id)myMethod:(id)val;
{
return obj;
}
@end
更に複数の引数を渡したいのならば、可変長かラベルを利用する事になる。
註:Xcode で Cocoa アプリケーションを開発するならば、メソッドを呼び出す時は、ヘッダが解析された後に入力支援システムによって補助が入り、どこにコロンを打ちどこにどんな変数がと云う事を悩む必要がなくなる。だから今一番憶えておきたいのは、寧ろメソッドの記述方法である。
C における可変長と同じく、可変長引数を id 型にすれば
+(void)myClassMethod:(id)objects, ...;
と書ける。
Objective-C でもっとも一般的な複数引数の記述・利用法がラベルである。Objective-C では、その他の言語での引数がただの変数の羅列になるのとは対照的に、引数に名前を指定する。この名前をラベルと言う。座標を渡すメソッドを考えたならば、
[receiver myMethod:val withX:x withY:y];
−(id)myMethod:(id)val withX:(int)x withY:(int)y;
と書く。ラベルの導入は code を著しく簡明にする。C の関数等で myMethod(val,num1,num2); と書くと引数の意味が取りづらいが、Objective-C では上にある様に見目に分り易くなっている。つまり打鍵数は増えるかもしれないが、それでもなお利点が大きいと判断された為に、ラベルは導入されたのである。
またこのラベルは同じメソッドで幾通りも用意できる。今上に示した様なメソッドはドキュメント等では myMethod:withX:withY: と書く事で略記されているが、myMethod:withX:withY:withZ: なるメソッドを作りたいと思った時、myMethod:withX:withY: と myMethod:withX:withY:withZ: は共存できる。
註:一つだけの引数の場合はラベルがない様にみえるが、次の様な解釈ができる。すなわち上のメッセージは「myMethod:、withX:、withY: と云う三つのラベルを有しており、myMethod: の如きメソッド名の様にみえる表記はラベルを表しているに過ぎぬ」と言う物である。つまりレシーバに引数を送信する為には必ずラベルをつけたメッセージを送るとみれるわけ。なおラベル自体は文法上必須ではなく、メソッド名だけでも良いが、上に挙げた「見目に分り易い」ことが重要なのである。
Objective-C は柔軟すぎる言語である。中にはかなり邪悪或いは病的な手段も存在するが、それらの使用は全てプログラマまかせとなっている。これは Objective-C が弱い型付けである事と同じ理由であろう。今は親クラス(superclass)のメソッドを上書き(override)する方法を示す。
<準備中>
註:上書きする場合はいちいちヘッダに書く必要は無いが、若し書きたいと思うのなら、書いても問題はない。
Cocoa API を見て行くと暗默上の了解が幾つもある。この事はもう少し後に扱うべき内容であるから、API をみて何かあると感じた時、規約がある事を思い出して欲しい。
今 NSString のメソッドを例にすると、
等が即座に指摘できる。これらは順にドキュメントを読んだりしながら、各が会得して欲しい事項である。
Objective-C におけるインスタンスは、C のポインタである。だから変数の型にはポインタを表す * が必須である。
NSObject *obj = [[NSObject alloc] init];
このインスタンスたるポインタに、Objective-C の Runtime が、メッセージを送受信してオブジェクト指向たらしめている。なんとなれば、id 型は void * 型と等価であると言う事だが、一つだけ違う点は、id 型が Objective-C のレシーバであると保証してくれる事である。だから任意のクラスのレシーバを利用したい場合は id 型を用いるとよい。
レシーバの初期化は上にあげた +alloc, -init がもっとも基本的な物であるが、ファクトリメソッドが用意されることもある。使い分けは後々メモリ管理で論じるが、今はメモリリークは一切気にせずに、初期化法を考えてみる。
alloc と init は NSObject(より正確には NSObject Protocol)で定義されており、Objective-C の根幹を成すメソッドの一つである。alloc はレシーバにメモリを割り当てるが、この alloc は上書きしてはならない。init はこれを初期化するが、自身の初期化をコードを用いたいならば、上書きしてもよい。
註:オブジェクトの所有權についてはメモリ管理をみて欲しい。より詳しい初期化についての事はそこで述べることになる。
註:+new は、+alloc と -init を呼出すに等しい。
Objective-C では、nil に存在しないメッセージを送信しても、エラーが発生しない。これは通常のレシーバでは致命的なエラーとなる事と異なる結果を導くので注意して欲しい。
[nil myMethod];// エラーにならない
[receiver myMethod2]; // myMethod2が未定義ならばエラー
これは例えば delegate で nil を設定しても呼出しに悩む必要がない等、ただメッセージを送信するだけの用途のコードのエラー処理を短縮する事になるだろう。
Objective-C のメモリ管理は「参照カウント」と「ガベージコレクション」が用意される。Mac OS X はメモリリークしたメモリであれプロセス終了時に回収してくれるが、実行時にメモリを大量に利用する場合メモリ確保が困難になるかもしれない。だからなるべくメモリリークを防ぐコードを書くべきである。ここでは参照カウントにまつわるメソッド、つまり NSObject に実装されている NSObject Protocol の +alloc、-init、-retain、-release、-autorelease、そして NSAutoreleasePool class を解説する。なおガベージコレクションを利用する場合は Mac OS 10.5 以降に導入されたため以前のバージョンと互換性がない事に注意。
一般に上から順に実行する。任意の瞬間にオブジェクトを解放するならば、retain によりカウンタを増し release によりカウンタを減らせばよい。release は参照カウントが 0 になればオブジェクトを解放してくれる。
註:-retainCount によりカウンタを取得できる。NSLog(@"%d", [receiver retainCount]); と呼出して、カウンタの動作を確認して欲しい。
註:ガベージコレクションでは、nil を代入する事が、解放の目安となる。従って不要な変数には nil を代入すること。
註:iOS では、メモリが限られた資源であるため、ガベージコレクションが利用できない。
クラスの初期化には +load, +initialize を用いる。これはただ一度だけ呼出される。+load は Runtime 上に追加された時に呼出され、+initialize は初めてそのクラスを用いる時に呼出される。
インスタンスの初期化は -init 等の指定イニシャライザで行う。
メモリ解放時の処理は -dealloc, -finalize で行う。-dealloc は参照カウンタにおける解放、-finalize はガベージコレクタにおける解放で自動的に呼出される。但し、必ずしも呼出されるとは考えない方が良いので、アプリケーションの終了時に必要な処理を記述する事は避けた方がよい(譬えば、NSApplication に -terminate とすると、OS が強制的にメモリを回収するらしく、-dealloc を呼ぶ事がない)。
註:-dealloc は強制的にメモリを解放する手段ともなる。
上で示した仕組みでは時に問題が起る。メソッドの戻り値としてメソッドが生成したオブジェクトを返す場合である。
−(id)myMethod
{
NSObject *val = [[NSObject alloc] init];
return val;
}
今この様なメソッドの戻り値を利用する場合を考える。この場合、戻り値を取得した時点でカウンタは 1 となり、-release しなければリークする。つまり、以下の様な戻り値を代入するコードを想定してみると、容易にリークする事がわかる。
NSLog([[receiver myMethod] description]);
戻り値のカウンタはこのままでは永遠に 1 のままであり、解放はなされない。では、仮に myMethod で -release してみればどうなるであろうか。これも上手くいかない。当然乍ら、-release を送信した時点で解放されてしまうからである。それではどうすればよいか。これらの解決策はただ一つ、autorelease を呼び、Autorelease pool に追加することである。
−(id)myMethod
{
NSObject *val = [[NSObject alloc] init];
return [val autorelease];
}
こうすると、Autorelease pool 上にあって且つカウンタが 1 となったインスタンスを取得できる。
註:以上の事から、指定イニシャライザで取得したインスタンスの場合は、代入的手段を直接行ってはならないとわかる。どうしてもその必要のある場合は、NSLog([[[[NSObject alloc] init] autorelease] description]); と、-autorelease の戻り値を利用する事で解決できる。
註:通常の開発では、NSApplication が NSAutoreleasePool のインスタンスを生成しているから、autorelease はこのままで動作する。しかし Cocoa Application 以外の Cocoa を利用した開発では、自前の Autorelease pool を用意する必要がある(若し用意がないまま -autorelease を呼べば NSObject autoreleased with no pool in place と警告が出る)。また、任意に -drain を呼んでやる必要がでてくる。この -drain は pool のインスタンスに -release を送信する。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
/* code */
[pool drain];
但し、NSAutoreleasePool のインスタンス自体には release を送ってはならない。
註:Cocoa Application では、event loop の終了と同時にその都度 -drain が送信される。
簡易コンストラクタは autorelease されたオブジェクトを返す。
指定イニシャライザは、実態が唯一でなければならず、しかも retain されているオブジェクトを返す。取得した側で責任を持ち release する必要がある。
これらで取得する場合は、簡易コンストラクタと異なり、retain されているので、オブジェクトが不用になったならば、プログラマは release せねばならない。しかしそれ以外の手段(メソッド)で取得できるオブジェクトは autorelease されている。この点に注意されたい。
Cocoa における Root class の一つ。実は NSObject のメソッドは NSObject Protocol と NSObject Class のメソッドとに分類できる。
註:Cocoa における Root class は NSObject Protocol を適用する事になる。NSObject Protocol には基本的なメソッドが全て列挙されているから。NSProxy 等。
Ruby における module みたいな奴。任意のクラスに幾らでも適用できる。Ruby module が実装ならば、Obj-C Protocol は宣言に過ぎない。手法が明示的なる物と暗默なる物の二種類ある。
カテゴリは定義済のクラスを拡張できる、Objective-C でも一・二を争う邪悪で、そして便利な手段である。例えば今 NSString に base64 encoding のメソッドをつけ加えてみたいと思うならば、カテゴリを用いることで実現できる。
よく使う構造体は、おそらく以下の四つ。
これらは KVC でも始めからサポートされる構造体で、特別な扱いを受けている。
配列なんかで用いる。NSMakeRange で生成できる。
NSView 及びそのサブクラスでよく使うことになる。NSMakeSize。
Objective-C におけるコンパイラ及びプリプロセッサへの命令について。C 標準は勿論のこと、幾らかの拡張がある。それが以下である。
#import は #include に同じだが、一度しか読み込まない点が異なる。// は行末迄コメントと解する。
互換性確保の為に @ で書き始めると言うのは、@interface の紹介をした際に書いた。今クラス(そしてそのカテゴリ、プロトコル)を宣言する手段をみれば、
があって、必ず@end で宣言を閉じる。そのインスタンス変数の可視性は次の宣言で示す。
初期設定は@protectedである。例外処理は、
@finally が強制実行部位で、あとはお馴染みか。更に特定の目的が為、次が用意される。
一応説明が必要なのは @"string" であろう(其れ以外は取り急ぎ必要がないから述べない)。@"string" は quoted な部位を NSString class のレシーバとして処理する構造である。@"abc" と書けば、abc なる文字を保持した NSString class のインスタンスが const として生成される。しかも(同じ stack であればだと記憶するが)@"string" を同メソッド内で何度呼びだしても、同じインスタンスを返してくれる。
註:インスタンス変数の可視性は、コンパイラが警告を出す申しわけ程度の物であり、完全な可視性を保証するわけではない。また若し完全な可視性が実現されたとしても、カテゴリを用いれば、どの様な未公開インスタンス変数であれ参照・変更が可能であり、可視性は無意味である。
註:Objective-C 2.0 から、@property, @synthesize, @optional, @required 等が追加された。
言語仕様に明るいわけではないが、例えば NSFastEnumeration Protocol の登場以降(Mac OS X 10.5以降)では、コンパイラは C 標準にない for 文をサポートしれくれる。これもディレクティブである。たまに C 標準ではない物があるとすれば、現在のプログラミングでは一般的な表記を、実用に利する為に取り入れた表記群であろう。
Mac OS X のメモリ確保方法について。malloc_zone_malloc とかの話。malloc.h みて下さい。
Xcode は Apple による工夫によって挙動に「癖」がある。
SDK を意識することは結構重要で、API の統廃合は結構よくある。最新の Document をみるか、API のヘッダを読めば書いてある。LEGASY とある場合は、なるべく使わない方がよく、新機能新API らしい物は当然互換性の障害となるから注意である。
恐らく debug になっているので、「出荷」するときは release にするように。ビルド構成は、ビルド時に指定するコンパイラオプション等を保存した物で、任意に切替ることができる。
作る実行ファイルのこと。ビルド前は存在しないから赤い文字で表記される。
<デバッガの使い方とか mark の仕方を書きたいのだけれど?>
Cocoa Application では precompile と呼ばれる工程がある。pch ファイルは、C++ でもお馴染みかもしれないが、Precompiled Header の事である。precompile とは文字通り compile の前段階のことで、簡単に言うと、キャッシュを用意する事で Cocoa Application のビルド時間が短縮できる。Xcode でこれを編集することはまずないので、飾り程度に認識しておくとよい。
ただこのキャッシュは 30MB 程度あって軽くない。10.4 以前なら /Library/Caches/Xcode に、10.5 以降ならば /private/var/folders 下にある -Caches-/com.apple.Xcode.501 に纏まってある。たくさん Cocoa Application を作っているとこのサイズは途轍もない物になるので、不安になればたまに削除すると宜しい。尚 /private/var/folders 以下は、人によりフォルダが異なるし、501 は UID なので、若し UID が 502 ならば com.apple.Xcode.502 になる場合もあるかもしれない。
@"string" でわかるように、const は使用可能。static は singleton で用いる。
インスタンス変数は C での構造体のメンバの様に宣言運用できる。例としてあげれば、@interface 中に int nums[10]; とできる。
メソッドを呼出す行為は、レシーバの情報から関数ポインタを取出し、それを呼出しているに過ぎない。今 <objc/objc-class.h> を import した上で、
id obj = [self getObject]; Method method; Class class = [self class]; SEL name = @selector(myMethod:); method = class_getInstanceMethod(class, name); #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5// OBJC2 UNAVAILABLE method->method_imp(self, name, obj); #else method_getImplementation(method)(self, name, obj); #endif
とすれば、これは [self myMethod:obj]; と同等である。ここで #if を用いるのは、Method のメンバにアクセスできるのは、32 bit 環境且つ Mac OS X 10.4 以前のみだから(正確には Objective-C 2.0 で削除された仕様であるから)。尚、メソッドを上書きしていなければ、self とは別のインスタンス等でも関数ポインタは同一である。若し同一かどうか不安であれば、NSLog(@"%p", method->method_imp); として確かめてみればよい。
註:例えば method を NSValue のインスタンスに格納して後々利用しようと思うならば、name が欲しい事もある。その時は、method->method_name 或いは method_getName(method) とすればよい。
Mac OS X における標準的開発環境が Xcode とそのパッケージである。
Cocoa アプリケーションでは、GUI 部品(及び Controller Object)をシリアライズした Nib ファイルを読み込むことで、GUI 生成に関するコードを必要としない開発を実現している。またそれに関する Controller Object を含めることで、GUI 部品からのイベントやアクションへの対処が可能となる。
これらのファイルは、任意の瞬間に、任意の方法で読み込むことができる。
読み込まれた Nib ファイルにおいては、保存されたオブジェクトがインスタンス化され、インスタンス化後に NSNibAwaking Protocol の -(void)awakeFromNib が呼ばれる。
Project 中の info.plist の NSMainNibFile、或いは Xcode でターゲットを選択し『情報』ウインドウを開き『プロパティ』タブの『主要Nibファイル』、として指定されているファイル名が、Cocoa アプリケーション起動時に必ず読み込まれる Nib ファイルである。たとえば Menu Bar に表示される Main menu は主要Nibファイル中でシリアライズされている。
【重要な内容ですが、ココに纏めて書きます】Outlet はインスタンス、Action はインスタンスのメソッド。
註:IBOutlet 及び IBAction は C のマクロである。IBOutlet はコンパイル時に無視され、IBAction は void * を表す。
Interface Builder の Library に Object と呼ばれる部品があるから、それをドロップすると、立方形のインスタンスが追加される。これがもっとも標準的なコントローラの追加方法である。コントローラの役目はいろいろあるが、例えば GUI 部品へのアクションは、大抵の場合、こうして追加したコントローラに送信することになる。
例:
Interface Builder の使い方は言葉では表現しづらい。一応全て終了したならば、「ビルドと実行」を選び、起動させる。Xcode の「実行 > コンソール」からコンソールを開き、表示されたウインドウのボタンをクリックするとメッセージが表示される筈である。このコンソールには、NSLog の出力の他にエラーメッセージ等も表示されるから、出力には常に注意を払うこと。
参考:
基本的なことが書いてある。『Cocoa Fundamentals Guide > オブジェクトとの通信 > ターゲットとアクションの設定』をみること。
註:Cocoa Fundamentals Guide にもあるが、コードでアクションとターゲットを設定することもできる。 NSMainNibFile や Nib ファイルについて上で書いたように、
// AppController.h @interface AppController : NSObject { IBOutlet NSButton *button; // AppController.m -(void)awakeFromNib{ [button setTarget:self]; [button setAction:@selector(myMessage:)]; }
と書き加え、定義した outlet が機能するようにボタンへ接続しておけばよい。outlet の接続を行うことで、 GUI 部品のインスタンスをコントローラ上で取得できるようになる。
課題:『参考』『註』を見ながら、ウィンドウに先程のボタンを無効にするボタンを新たに追加すること。新たなアクションメソッドを必要とする筈である。尚、NSButton には、superclass の NSControl において -setEnabled:(BOOL)flag が定義されている。・・・・課題風にするなら、独立したページでチュートリアル式にした方が宜しいかもしれない。
Nib ファイルにはその所有者である File's Owner が必ず設定される。それは、
等である。
標準でかなり多くのアクションが Main menu から設定されているが、これら全ては最終的に他のオブジェクトへの呼び出しとなる。その仕組みは responder chain と呼ばれる。
Application Kit (AppKit) には、さまざまな御約束がある。ある程度馴れてくると、ヘッダをみて、通知や委譲がないか確かめたり、クラスの使い方がわかるようになる。
API はよく使う物を特に説明する。ただし Core Data については割愛する。
恐らくもっとも頻繁に作られるクラス。
NSDictionary や NSArray に int や nil や 構造体等を納めたい場合にはこれらを用いる。NSValue でラップできる物については @encode() 参照。
dataSource と delegate をうまく使いこなすこと。
NSTableView 同様。
カスタムビューに何か御用ですか?
key-window と main-window がある。key-window とは、Mac OS X において、唯一キーボードからの入力を受け付ける window。通常 NSWindowController のサブクラスを File's owner として nib ファイルを読み込み NSWindow を生成することになるが、NSBundle で読み込み生成してもよい。
NSWindow *w = [myView window]; BOOL ended = [w makeFirstResponder:w];
Document-based Application で大活躍。[NSDocumentController sharedDocumentController];
[NSApplication sharedApplication];
要約として次に用意されている。構文もまた参照せよ。
Cocoa とは、Foudation Frameworks、CoreData Frameworks、AppKit Frameworks により構成される Objective-C の API 群である。
Foudation は、そのまま「基層」の意である。化粧をする貴方には下地(ファンデ)でおなじみ。Cocoa でもっとも酷使される可哀想なやつ。一方で、これの扱いになれれば Cocoa API はグっとわかりやすいものになる。
抽象的で申し訳ないが、要するには Cocoa の頻出データ型を定め頻出する機能を纏めたやつである。MVC でいえば Model の提供が主で Controller の技術的補助(オブザーバなど)、View に渡すデータなどの役割を担う健気なやつ。
AppKit は、ApplicationKit の略である。Kit の接尾辞は Apple では拡張的であれど基本的な API 群につけるらしく、たとえば Safari の KHTML エンジンは WebKit Frameworks で実装される。Cocoa にラップされた QuickTime API は QTKit である。
感覚をつかむためにも、まずは手元にある Interface Builder を起動させてみて欲しい。起動させれば何か作るかと聞いてくるから、Cocoa > Window を選ぶ。Untitled とある画面にはタブがあり、Instances というタブが選択されているはずである。 Cocoa > Window の nib テンプレートには File's Owner、First Responder、Window とある訳だが、Window のアイコンをダブルクリックして欲しい。 君の目の前には真っ白な「ウインドウ」が現れるだろう。多分表示されていると思うけれど、パレットが無ければメニューから「Tools > Palettes > Show Palettes ?/」を選びたまえ。次にこれもおそらく表示されてると思うけど、パレットにカラフルなイラストで彩られた上段のタブがなければ、右上のボタンで表示するように。
さあ簡単に説明しよう。これが Cocoa AppKit のインスタンス君たちである。ツベコベいわず、Controls、Text、Data、Containers などの名前付きのタブから部品をマウスで選び、先程私がダブルクリックしろと言い散らして君が放ったらかしにしてる「ウインドウ」にドラッグドロップせよ。 あないみじ。何か表示されるね? ああ、されないなら君の Mac OS X Developer Tools は欠陥品だから、抗議の電話をしたまえ。
これが "インスタンス" だ。君がこれから作るアプリケーションはこの Interface Builder で概観をデザインすることになる。
なおこの インスタンス 予定部品君たちは Cocoa の高度な抽象化により、君がコードで宣言しなくても Mac OS X によって勝手に初期化されて表示されることになる。君はではこれの値を変えるにはどうすれば、と思うだろうから簡単にいうと、委譲関係を設定するか Binding を利用するかになる。恐ろしいことにこれも全て Interface Builder で且つグラフィカルに設定できてしまう。 先程ウインドウに君はインスタンスを載せた。ボタンやらコンテナやらである。control キーを押しながらそのインスタンス部品をクリックしてマウスを動かしてみれくれ。何か線がニョロニョロ出てくるだろう? これが委譲関係を設定する一つの手段である。細かい事は割愛しようと思うが、便利でわかりやすいけどわかりにくい構造とならしめている Interface Builder の一端が垣間みれたのではないかな。
すでに他のオブジェクト指向を味はった貴方に説明するなら、これは Key-Value Coding Protocol(KVC)、Key-Value Observing Protocol(KVO) が活躍することになる。KVC は setter、getter、そして KVO は Observer パターンを実装するもので、NSObject をルートとするクラスには自動的に備わる機能になっている。protocol は Ruby 的な感じだと規約に当たる物で、こうしましょうという感じのメソッドの一群。protocol は Objective-C の文法的要素でもある。protocol を実装したクラスは、protocol の規約に従ひメソッドを定義せねばならない。ただしこれはインスタンスの構造に変更を加えるときのみ必要なだけで、単純に NSObject を継承したクラスでは KVC の protocol に適合していることになる。
先程載せたインスタンスらは nib ファイルに格納し保存される。インスタンスは plist 形式でシリアライズされており、Mac OS X のアプリケーションは起動時にこれを参照し復元して GUI 環境を提供してくれる。plist は Foudation の一部のクラスをシリアライズする物で記法は三種類ほど存在する。よく使うので別項で解説する。nib ファイルはアプリケーションバンドルに必ずあるので、「パッケージの内容を表示」して探してみて欲しい。
MVC でいえば、まさしく View を担当することは先程の Interface Builder でよくわかったかと思う。Controller もちょっち担当。
Core Data は、WebObjects の EOF(Enterprise Objects Framework)の流用である。さっぱりわからん? まあ聞いてくれ。Core ImageだとかCore Audio、Core Video、などのイメージがあるので、私はハードウェア的な実装なのかとつい勘違いしたが、これはすこぶるソフトウェア的である。Core の接頭辞は Carbon API のように C で実装された API をラップする存在に付けられるようである。先述の Foudation は Carbon API の Core Foundation をラップする。これらは、高速化を図るための思われる。
WebObjects はマイナーの嫌いがあるので説明せば、Mac OS X に無料でバンドルされる Java Servlet の類いである(なお元々は純粹なobjcだったがversion.5よりJavaになった)。EOF は WebObjects の売りのひとつで、 サーバ毎に Adaptor を巧みに使いわけなおかつ SQL をデベロッパが書く事無くして DataBase を操作する代物であった。
Core Data は、主に書類を扱う際に威力を発揮する。EOF との違いは、EOF は Java Servlet という用途に沿う実装だが、Core Data はデスクトップアプリケーションの為の API であるということである。そこらへんのことは Apple の Core Data の解説に詳しい。
長くなったけど、序論である。AppKit に項目を割いたのは Cocoa GUI に於いてとても重要であるからである。
また、下記リファレンスの URI に於いて CoreData を除き「ObjC_classic」とあるのは何故かと思われるかも知れないので付け足して置くと、Foundation と AppKit は OPENSTEP 時代からの遺産であって、Core Data は Mac OS X Tiger(v.10.4) より導入されたに過ぎない。 ゆえにクラシックなのである。
Foundation Framework Reference
AppKit Framework Reference
CoreData Framework Reference
Cocoa バインディング入門(邦訳版)
Core Data プログラミングガイド(邦訳版)
「キー値コーディング」及び「キー値監視」について(邦訳版)
Core Data のレビュー記事。KVC、KVO、Cocoa Bindingにも触れる「TigerのCocoaにみるMVCの完成 - スマートなデータモデルを実現するCore Data」(マイコミジャーナル・コラム)
Foudation Frameworks は Carbon の完全なラッパーとなって居る。幾つか憶えるべき最低限のものを挙げてみれば、それは NSString、NSArray、NSDictionary などのクラスである。まず、基本的なクラスを紹介する。
NSObject はルートクラスであり、次のプロトコルに "接続" する(Adopted Protocol)。
これらのプロトコルで実装されるのはインスタンスメソッドのみとなる。Cocoa では次のステップを踏みインスタンスを生成する。
NSObject *obj = [NSObject alloc]; [obj init]; [obj autorelease];
alloc はデザインパターンの Factory にあたり、Objective-C ではこのクラスメソッドがメモリ確保を行う。絶対に子クラスで上書きしてはならない。なお alloc は NSObject に実装されている。これを「割り当て」と呼ぶ。他の言語のように new Class という記法はないが、new メソッドが存在する(後述)
後述の NSArray や NSString といったクラスのクラスメソッドには次の物がある。
+ array + arrayWithArray + string + stringWithCString:encoding: + allocWithZone + newarray*** とあるのは NSArray の Factory であり、string*** とあるのは NSString の Factory にあたる。Cocoa の Factory は戻り値として初期化する前のインスタンスを返す。なお Objective-C では + を伴うメソッド定義がクラスメソッドを示す。
割り当てられた領域は NSZone クラスのインスタンスとして(zone メソッドにより)取得することができ、allocWithZone で生成すれば同じ領域を使用できる。また、参照カウント及びインスタンス変数の初期化を行う。ここに於いて isa インスタンス変数の決定という重大な過程を経過する。
init は初期化メソッドであり、インスタンスメソッドである。子クラスで継承できるが、親クラスのインスタンスメソッドを実行させること。NSObject に実装。
- (id)initWithArray:(NSArray *)array - (id)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)anotherDate - (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag - (id)initWithFrame:(NSRect)frameRectこれも同様に init*** という命名規則を持つ。これは NSSet、NSDate、以降はAppKit だが NSWindow、NSControl(及びNSView) などのメソッドである。
似たようなメソッドとしてinitializeがあるが、こちらは「クラス」の初期化を担う。一度しか呼ばれない(後述)。なお初期化した自身を返すのだが、 id 型を返すとあるのはこれを不特定の何か程度に認識して於いてほしい。C の為に存在し、Objective-C では id 型がオブジェクトを指す。
autorelease は自動で release する、すなわちメモリ解放の為の手続きである。最近の言語では garbage collection は最早定番とも言えるがこれも似た手段である。解放プールに入れると表現されるが、それはアプリケーション起動時に NSApplication が NSAutoreleasePool クラスのインスタンスを作り、これに叩き込んで監視するからである。
- autorelease - release - retain - retainCountNSObject Protocol によってこれらのインスタンスメソッドが定義される。autorelease は明示的に release をするのと変わらぬ効果を与える。retain は参照カウンタを増大させ release は参照カウンタを減少させるものだが、NSArray といったコレクションクラスに挿入すると自動的に retain が呼ばれ参照カウンタが増える仕組みになって居る。不要になれば release か autorelease を呼ばねばならない。
正確な意味での garbage collection は Objective-C 2.0 への拡張で組み込まれ Leopard より実装された。保守的(conservative)な GC である。
他にインスタンス生成に於いて俯瞰せしめた際に見渡せることを書連ねる。Cocoa では isa インスタンス変数を組み込むことでクラスを識別する。こういったクラスに関する処理で特殊なものがあるから紹介する。
### NSObject: Adopted Protocol ## NSObject Protocol # Identifying and Comparing Objects - isEqual - hash - self # Describing Objects - description # Sending Messages - performSelector # Determining Allocation Zones - zone # Identifying Proxies - isProxy ### NSObject Class: Tasks # Initializing a Class + initialize + load # Creating, Copying, and Deallocating Objects + new - dealloc - finalize ### NSString Class: Tasks - hash - isEqualToStringあるプロセスに於いて NSMutableString クラス(NSMutableString > NSString > NSObject)が初めて呼びだされたとして、その時に NSMutableString のクラスメソッドたる initialize が自動的に呼び出される。親クラスたる NSString クラスの initialize が呼び出されていない場合も同様である。また、load クラスメソッドもクラスがプログラムに読み込まれたことを告げるために initialize を呼ぶ前に呼び出される。すなわち次の順となる。
[NSObject load]; [NSObject initialize]; [NSString load]; [NSString initialize]; [NSMutableString load]; [NSMutableString initialize];new クラスメソッドは次の式と等価である
[NSObject new]; # Pattern1 NSObject *obj = [[NSObject alloc] init]; # Pattern2 NSObject *obj = [[NSObject alloc] init];ただし、new に渡された引数は init にも渡されることになり、alloc と異なり子クラスで上書きしても構わない。
dealloc はメモリ解放を行い、release により呼び出される。明示的に dealloc を呼ぶ事は無い。独自のメモリ解放機構を備えるなど子クラスで再定義できる。
finalize は dealloc に代わり呼び出される。GC が有効なとき、autorelease を呼んだときなど(よくわからんから調査中)。
description 則ち「説明」とあるけど、これは NSString にインスタンスを変換しこれを返す。いわゆる「toString」「to_s」 などのメソッドと考えて欲しい。isEqual は何が「イコールか?」と云うとインスタンスの hash メソッドを用いて比較した真偽値を返す。たとえば、NSString クラスでは hash メソッドが再定義され、同時に isEqualToString が追加された。isEqualToArray、isEqualToDate、isEqualToSet など似たメソッドはたくさんある。self はインスタンス自身を返すメソッド。
zone は先述の NSZone を作って返すメソッド。これは allocWithZone、copyWithZone といったようなクラスメソッドなどの引数となる。
performSelector は SEL、IMP、Methodの三つの型を理解する為に挙げた。一部の例が Ruby で申し訳ないが、次のような物である
- SEL型:セレクタ。C の構造体のポインタ型(Opaque、実態はchar型)。Ruby だと Symbol クラス。
- IMP型:メソッドの実装。C の関数ポインタ。Ruby だと Proc クラスなどか。
- Method型:メソッド。C の構造体で、SEL型・IMP型・char型を格納する。
### objc-class.h typedef struct objc_method *Method; struct objc_method { SEL method_name; char *method_types; IMP method_imp; }; ### objc.h typedef struct objc_selector *SEL; typedef id (*IMP)(id, SEL, ...); ### performSelector は次のように使う(self メソッドを呼ぶ) SEL *aSelector = @selector(:self); # @selector は ObjC の言語構造である # なおこれらの@で始まるこれらの文字をコンパイラへの「ディレクティブ」と呼び、他にも幾つかある。 SEL *aSelector = NSSelectorFromString(@"self"); # この定義済関数でNSString から SEL に変換できる。 # 同様に@"string" はディレクティブであり、呼出したモヂュール内部での NSString のオブジェクト定数となる。 [obj performSelector: aSelector]; # 送信先からの戻り値を返す。id 型なので何でもアリ。 NSString *method = NSStringFromSelector(aSelector); # この定義済関数で SEL から NSString に変換できるisProxy が NSObject Protocol にわざわざ定義されているのは、NSProxy クラスがルートクラスだからである。NSProxy は NSObject Protocol を実装するけれども、他のクラスと異なり NSObject を継承しない。クラスとプロトコルの違いを強調する為に項を割いた。
Cocoa基礎ガイド > Cocoaオブジェクト > オブジェクトの作成
Objective-C 2.0プログラミング言語 >ランタイムシステム > オブジェクトの割り当てと初期化
Objective-C 2.0プログラミング言語 > クラス
Objective-C 2.0プログラミング言語 > メッセージングの仕組み
Objective-C 2.0プログラミング言語 > 付録 A: 言語の要約 > コンパイラのディレクティブ
マイコミの「ダイナミックObjective-C」コラム。オブジェクトについては14~17あたりをみてほしい。メソッドについては18~22。GC については96~100。
Cocoaプログラミングの話題。Cocoaのメモリ管理など参照。
かんたんObjective-C
Foundation クラスの最適化
Cocoa では次のコレクションが用意される。継承関係も示した
NSArray は配列、NSDictionary は辞書(ハッシュ)、NSSet は集合(集合、重複を許さぬ配列)です。このクラスに格納したオブジェクトは変更することができない(Immutable)。変更すること(Mutable)を望む場合は次のクラスを使用する。
またコレクションは次のプロトコルを実装する。
NSFastEnumeration は Objective-C 2.0 より実装された高速列挙(Leopard 以降)のためのもので次の表記が可能になる。
for(id objectItem in array){ .... }
NSCopying、NSMutableCopying は複製するためのプロトコル。NSObject に複製用メソッドは定義されているものの、NSObject はこのプロトコルに関しては実装していない。
### NSObject Class: Tasks - copy + copyWithZone - mutableCopy + mutableCopyWithZone ### NSArray, NSDictionary, NSSet: Adopted Protocol ## NSCopying Protocol - copyWithZone ## NSMutableCopying Protocol - mutableCopyWithZone
Objective-C 2.0プログラミング言語 > 高速列挙
一方で次のようなデータ型的な物がある。Primitive とあるように基本である Foundation の更に基本構成員たちである。
更に次のような型が定義される。これは C の構造体という位置づけになる。
NSRange は特に Foundation に於いて頻出である。これら構造体は CGFloat、NSUInteger などの型を格納する。そんなにややこしいものでもないので「Foundation Data Types Reference」及びヘッダファイルより抜粋。
typedef struct _NSPoint { CGFloat x; CGFloat y; } NSPoint; typedef struct _NSRect { NSPoint origin; NSSize size; } NSRect; typedef struct _NSSize { CGFloat width; CGFloat height; } NSSize; typedef struct _NSRange { NSUInteger location; NSUInteger length; } NSRange; #if __LP64__ typedef long NSInteger; typedef unsigned long NSUInteger; #else typedef int NSInteger; typedef unsigned int NSUInteger; endif
NSUInteger は 64bit な Mac と 32bit な Mac に於いての工夫を施すために定義された型となる(註:つまりvoid *、ポインタと同じ大きさ)。
//CGBase.h typedef float CGFloat;// 32-bit typedef double CGFloat;// 64-bit
CGFloat 型は「CGGeometry Reference(CGBase.h)」に説明があるけれども、要するにfloat(double)型である。これらを列挙した理由は NSValue がサポートする物だからに他ならない。NSValue がサポートできる型の値は KVC の自動ラッピング機構を利用できる。
また次の定義済関数で各々の型を作ることができる。
NSMakePoint(x, y) NSMakeRect(origin,size) NSMakeSize(width, height) NSMakeRange(location, length)
本家本元のAppleによる 「Objective-C プログラミング言語」の解説。
ダイナミックObjective-C(マイコミジャーナル・コラム)。Cocoa での Objective-C がいかに動的な工夫があるか、いかにデザインパターンを実装するかを綴る。