2013年8月1日木曜日

Objective-Cの勉強(4);メソッド実体実装


メソッド実体実装



メソッドの実体実装は、@implementationで行う。

@implementation クラス名
+(リターン値)関数名:(引数型)引数    // クラスメソッド
{
    return(リターン値); // 当然(void)なら省略
}
-(リターン型)関数名:(引数型)引数    // インスタンスメソッド
{
    return(リターン値);
}
命名規則はCと同じだが、習慣的にメソッド名は小文字から始めるらしい。
(Cとしての通常変数名とメソッド名は同じになっても良い。でも分けた方がわかりやすいのは当然。)


クラス宣言と実体記述は、ファイルを分離するのが「原則」である。
ファイル名は同じで、拡張子はヘッダーは.hでCと同じだけど、実体は.mと異なる。
実はXcodeでは.cと.mでコンパイル時の挙動がかなり変わる。
自動的にimport/includeするファイルが異なり、また.c内では、ごく一部を除きObjective-Cによる拡張部分は使えない。
<UIKit.h>のimportも許されないし、@"~"もエラーになる。と言うことは、それを使うNSLog()などの関数も使えないと言うことを意味する(代わりにprintf()を使う)。
先に書いたとおり、BOOLも.c内では使えない。
例外は、私の知る限りでは#importくらいである。これはプリプロセッサ命令だからだと思される。
逆に、.m内ではCの全てが使える。従って、基本的には.mで統一しておいた方が面倒がなくて良い。


Objective-Cではインスタンスもしくはクラスにメッセージを送ることでメソッドを実行させる。
この「メッセージを送る」式はCに対して拡張された部分なので、識別できるように[]で括る。これをメッセージ式という。
あるメソッド中で同じクラス内の別のメソッドを呼び出す場合は[self 関数]と記述する
Objective-Cにおいて、メソッドの呼び出しは基本「オブジェクトへのメッセージ送信」の形を取るからである。
自分自身クラス中である場合にはselfが必要となる(メソッド名(引数)という形で呼び出すことは出来ない)。
「基本的に」Cで記述された部分とObjective-Cで記述された部分は切り離されているからである。
その垣根を越える方法はあるが、難しいので省略。

//                                _____引数型
-(int) CheckDropPoint:(int)x arg2:(int)y
//                    ~~|~~  ~|~~
//                   引数型  第2引数ラベル
{
    ~
    return(1);
}
- (int) Cascades
{
    while ([self CheckDropPoint:x arg2:y]==0) { // ←ここ要注意
        r=[super getScrn:x+1 arg2:y];           // ←親クラスのメソッドに対してはメッセージで送る
        ~
    }
    return(0);
}

クラスインスタンスを保存しておく必要がない場合は、その確保宣言そのものを省略して、
[[Class alloc] method]
と記述することも出来る。


プログラムの実行がmain()からなのはCと同じ。
iOSアプリケーションの場合はもう1段上のクラスからの呼び出し処理が入るのだけど、
その辺りは「アプリケーション実装の勉強」で。

int main()
{
    id obj = [Class alloc];
    [obj method];
    return (0); // iOS上ではexit()は使えない
}
メソッドは、クラスを@implementationで実装すれば使えるようになる。
処理実体がメモリ上に存在するということである。
しかし、@implementationしてもメンバー変数は確保されない。
クラスインスタンスの実確保は別途行う必要があるということである。
メンバー変数にアクセスするメソッド=インスタンスメソッドはクラスの実体が確保されるまで実行できないが、クラスの構造だけに依存するメソッド=クラスメソッドはこの時点で実行できる。
この「実体の存在」の認識はObjective-Cでは非常に重要となる。



式の記述上は似ているメンバー変数とグローバル変数には、実は根本的な違いがある。
「メンバー変数はアドレスが固定でない」ということである。


グローバル変数は、コンパイラがコンパイル時にそのアドレスを確定する。
(正確にはリンカーやOSがプログラムをメモリ上に読み込み際に確定される)。
だから、プログラム内どこからアクセスしても同じアドレスにアクセスする。
しかし、クラスメンバはインスタンスによってアドレスが変わる。
だから、記述上は同じに見えても実行時のアクセスという意味では全く異なるわけである。


Cで構造体の制御関数を作るときは、必ず、引数に構造体(多くの場合そのアドレス)を渡すことが必須となる。
制御関数内でのメンバーへのアクセスは、その引数のアドレスからの相対アドレスによる。
ところがObjective-Cのクラス定義では@implementationにクラスのアドレスを渡す記述はない。ないが、内部的には、クラス内のメソッドが実行されるときには、自動的にその時使うクラスインスタンスのアドレスが渡される。
逆に言えば、クラスインスタンスのアドレス確定=領域確保は絶対必須であるということである。


クラス実装はあくまでひな形であって、クラスそのものの領域確定宣言そのものではない。このあたり、慣れるまではクラス実装を書いただけで、インスタンスがあるような錯覚を起こすことがあるので要注意である。


ここからは少し、高度なお話。
iOSは実はかなり高度にマルチタスクを行う。少なくとも、メソッド単位ではマルチタスクが基本になっているので、「このメソッドを呼び出して、返ってきてからこの処理が走って・・・」と考えてはいけない。メソッドを呼び出したら、帰りを待たずに呼び出し側は次の処理を実行してしまう、こともある。どこが待ってどこが待たないかは実のところはっきり理解してないのだが、たぶん「リターン値を持たないメソッドは並行動作する」で間違いないと思う。
少なくともUIKitは呼び出したら即並行動作するので、「表示されたはずだから」と想定してその後の処理を書いてはいけない。
表示が完了したらデリゲートが呼び出されるので、そこで後の処理を書く必要がある。
あまりに高度な並行動作なので、頭の中でなかなか追い切れなかったりする。
自分でマルチスレッドなどを書いたらさらに複雑になるわけで、μITRONのようなイベントドリブン型マルチタスクに慣れているなら、考え方を根本的に変える必要があるかもしれない。(私がそうだった)。
絶対に処理終了を待つ必要がある場合は、別途フラグなどで判定して待つ必要がある。
しかし、ループで単純に待ったら他のスレッドが走らなくなるので、いろいろと対策を入れる必要がある。

拙作X-BASIC for iOSはマルチタスクでないプログラム動作を実現するために、このあたりがかなり複雑になっている。実はBASICのコア自体がバックグラウンドで動いてたりするのだが、その辺の話は、そうさなぁ、1万本くらい売れたら書くと言うことで(^_^;)。


0 件のコメント:

コメントを投稿