全て独学で覚えたので、世間一般の認識とは違う部分があると思います。
私はアセンブラ→C言語→Objective-Cあがりで、特にCで考えることに慣れているため、それとの対比を多く行っています。
基本
まずは用語と基本を覚える。Objective-Cおよびオブジェクト指向言語特有の名称が多いが、覚えやすくするためにCの名称に置き換えることもする。その対応表の作成は後日に行うかもしれない。
要するにここはC言語は習得しているが、C++は勉強したけどあきらめた人(=用語ぐらいは少しは解る)向けで、「普通」とは異なるので注意(自分以外の誰が読んでんねん、という突っ込みは無視^_^;)。
まずは、絶対覚えなければならない基本用語。
オブジェクト | メモリ上に配置されるワークエリアの一般名称 |
クラス | オブジェクトのひな形(構造定義書) Cの構造体に近く、その中に制御関数;メソッドも含む |
メソッド | クラスの専用制御関数 |
インスタンス | クラスを実際にメモリ上に割り付けた「オブジェクト」実体 |
インスタンス化 | クラスを定義しただけでは構造体宣言と同じく実体は存在しないので、 それを元に変数を宣言、実体をメモリ上に確保すること |
継承 | あるクラスの機能を受け継いでで新規のクラスを定義すること 親クラス(スーパークラス)の全ての機能を受け継いだ上で、新機能または置き換え分のみ実装すればいい |
オーバーライド | 既存メソッドを同名メソッドで再実装すること。 基本的には、子クラスで親クラスに存在するメソッドを同名メソッドで再実装する。 |
レシーバー | メソッドを実行するインスタンスのこと。 |
Cの構造体のメンバー関数は「呼び出す」というし、アセンブラレベルでもCallするが、Objective-Cのメソッドは「メッセージを送る」という。
実はObjective-Cの基本にして重要な思想がここに現れている。Cのメンバー関数は(基本的に)構造体に固定されているが、メソッドは受信するオブジェクトを選択することが出来る。
異なるクラスに同じメソッド(名)を実装した場合、それは「同じメッセージを受信できるクラス群」として扱われる。そして、あるメッセージを送信した場合、それを実際に受信するクラスによって動作を切り替えることが出来る。
Cでなら、同じメンバー名関数を持つ構造体群があったときに、場合によって構造体側を切り替える場合にはswitch caseか関数の実行アドレスを持つ配列に入れて切り替えるしかないが、Objective-Cでは受信するクラス名を変数に出来るため、このあたりが実に簡潔に記述できる。
この両者の「基本概念の向きの違い」を覚えておかなければならない。それが「オブジェクト指向」である(この場合「思考」の方が合ってそうだけど)。
Objective-CはCの完全な拡張であるので、一部に制約が付いたC++とは異なり、Cで使えた書式は全て使える。
変数の型も同じだが、typedefにより、見かけ上いくつか追加がある。
id | オブジェクトを示す汎用変数(正確にはインスタンスのアドレス=ポインタ) |
Class | クラスオブジェクト(クラスの定義内容そのものを持つオブジェクト)へのポインタ |
SEL | メソッドのセレクタ値 |
IMP | メソッドの実行アドレス(ポインタ) |
BOOL | YES/NOのどちらかの値を持つ型 |
また、以下の定数もobj.hをimportすることで使える(標準定数)。
定数名 | 用途・意味 | 定義 |
nil | CにおけるNULLをidに拡張したものと思えば正解。終端やエラーを示すのに多く用いられる | (id)NULL |
YES/true | BOOLの肯定 | (BOOL)1 |
NO/false | BOOLの否定 | (BOOL)0 |
NSNotFound | 要素を探したが見つからないとき | 開発環境によって異なる |
BOOLはCでも定義して使っている人が多いけど、私は使わない主義であった。
関数の改良時によく、それ以外のリターン値を返すようにしてたからである。でも今は「区別しといた方がいい」派になっている。「年々歳々人同じからず」である。
余談。実はCocoa Touch上には真偽値を示す型が複数ある。BOOL/Boolean/boolである。NS/UIクラスで使われているのはBOOL、しかし、plistという物で使われているのはBooleanであったりする。
Boolean/boolではtrue/falseが値として使われるが、YES=true/NO=falseである。
boolがISO Cの標準真偽値型のようなので、正確にはifで判定されるのはbool型だと言えるが、混在してもコンパイラのチェック上でも動作でも特に問題は起こさない。まあ、NS/UIクラスと同じくBOOLで統一しておくのが無難かも知れない。BOOLはObjective-Cのソース、すなわち.m内でのみ使える。BooleanはCのソース、すなわち.Cでも使える。C内ではBOOLはエラーになる。Booleanはどちらでも使えると思う。
実際の値は同じだが、相互に使える使えないがあるので、注意が必要である。
Cの関数も全くの変更なしに呼び出すことが出来る(C++はextern "C"でヘッダーの書き換えが必要)が、一部機能は強化されている。
(古い)CとObjective-Cの細かい違いとしては、
- ヘッダーは「基本的に」#includeではなく#importで読み込む
プロトタイプ宣言が必須(前方参照不可)Xcode4.3では同一ファイル内では自動的に前方参照が解決されるようになった。- 一部の例外を除き、変数宣言をスコープの先頭で行う必要がない
#importはCのヘッダーで問題になっていた2重読み込みを自動的に排除してくれる。
ただし、#defineで定数の定義内容を書き換える(切り替える)タイプのヘッダーを#includeする場合は、#importに変更するとバグになる可能性がある。
Cのヘッダーは#includeで、Objective-Cのヘッダーは#importで読み込むのがいいかもしれない。
メソッドは、基本的に宣言と実体実装を別ファイルにするため、宣言ファイル(ヘッダー)を#importしておけばいいが、通常のC関数も、それがプロトタイプ宣言されているヘッダーを読み込んでおく必要がある。
(本当はクラスの独立性を高めるためらしいが、)ヘッダーを#importするのが面倒な場合で、クラス名であることを宣言するだけで良いなら、
@class クラス名1,クラス名2...;
というコンパイラディレクティブを使って宣言しておける。Cにおけるexternに近い。
「コンパイラディレクティブ」とは、Objective-Cで拡張されたプリプロセッサ命令である。プリプロセッサ命令であるため、コンパイル時には実内容に展開される=静的に決定される。
古いCの場合、ローカルに新規の変数を使う場合は必ず新しいスコープの先頭(宣言文以外が出てくるまで)に宣言しなければならなかったが、Objective-Cではこれがかなり緩くなる。本当は最近のCの規格ではそういうものらしい。
ただし、一部例外がある。
Cの場合 Objective-Cの場合 { { int x; int x; x=1; x=1; { int y; // 必要時に宣言すればよい int y; y=x+1; y=x+1; } } } Cの場合 Objective-Cの場合 switch (x) { 左に同じ case 1: switch内だけはスコープを新規に作る必要がある { (厳密にはcase文の直後に宣言するときのみ、新規スコープの宣言が必要。) int y; "Switch case is in protected scope"エラーが発生することもあるが、発生しないことのほうが多い。 y=x+1; } }という具合である。ただし、正確には上の例は変数yのスコープが異なる。Cの場合の記述は{}の中だけで有効だが、Objective-Cの記述では宣言以降全てである。従って、厳密にスコープを規定する場合は、Objective-CでもCと同じにする。単に「使うときに宣言したい」という意味の時のみ書き換えることが出来る。
各種名称の命名規則は基本的にCと同じであるが、「習慣的」には推奨がある。
クラス | 大文字から始める |
メソッド | 小文字から始める。_(アンダーバー)から始まる名称は禁止(アップル予約) |
カテゴリ | 大文字から始める |
プロトコル | 大文字から始める |
ラベル | 小文字から始める |
型名 | クラス名に準じる(だから見分けが付かなくてややこしい) |
変数 | 小文字から始める |
定数 | Cの場合大文字だけど、Objective-Cはクラス名に準じる(だから見分けが付かなくてややこしい) ただし、Google推奨記法ではkで始めると書いてある。アップル標準もそういうのが多い。 |
また、「同じ名前を付けたときにどうなんねん」については、以下の規則がある。
- メンバー変数名とメソッドの引数が同名の場合は、メンバー変数名が隠蔽される(要注意)。この場合、self->で回避できる(メンバーアドレスへのアクセスであり、プロパティ式ではない)
- メソッド名および引数ラベル名は予約語名と同じでもかまわない(int:とか)
- メソッド名および引数ラベル名は#define定数名と同じではいけない。プリプロセッサの方が先にソースを通すため
- クラス同じ名前のグローバル変数はあってはいけない(コンパイルエラー)
- 異なるクラス間では同じメソッド名を使うことが出来る
- 継承したクラスが親と同じメソッド名を持つ場合は、親のメソッドの代わりに実行される=オーバーライド
親のメソッドを実行する場合は、superを使う - 継承したクラスに親と同じメンバー変数名を持ってはいけない(コンパイルエラー)
- 異なるクラス間では同じメンバー変数名があってもかまわない
- メンバー変数とメソッド名が同じでもかまわない。というか積極的にそうしてメンバー変数への直アクセスをさせないようにする=プロパティ
- インスタンスメソッドには、クラスメソッドと同じ名前を付けてもいい(対象によって自動的に切り替わる)
- プロトコルには、クラス、カテゴリ、その他のものと同じ名前を付けてもいい
- 1つのクラスのカテゴリには、他のクラスのカテゴリと同じ名前を付けてもいい
Objective-Cでは同じ名前でも、機能さえ異なればちゃんと識別される場合が多いということだ。
同一機能であれば同じ名前にしておくとわかりやすいかもしれないが、だからといって全然異なる機能に同じ名前を付けるとバグの元なので避けるべきである。
0 件のコメント:
コメントを投稿