2ヶ月程度Objective-Cを勉強してきたが、少しずつその特徴が見えてきた。
カテゴリやプロトコル等、素晴らしい点がたくさんあるのだが、同時にいくつかの残念だとと思う点も見えてきた。
C言語であること
Objective-CはJavaやC#等のようにCのシンタクスをベースにしているものの、設計を1から行った言語などとは違いあくまでC言語が下地になっている。
objc-class.mやobjc-runtime.mのソースコードを見れば判るが、低レベルな処理はC言語やアセンブラで書かれており、そこの部分を調べるにはC言語の基礎知識が必要であり、敷居は低くない。
スカラ型とオブジェクト型の相互変換
初期のJavaでも問題になったが、Cで元々サポートしているスカラ型とObjective-Cで扱うオブジェクト型は相互の代入互換性は無いため、これらのデータを交換するには相互変換が必要になる。(相互変換を暗黙的に行うボクシング/アンボクシングは一部の機能しか実装されていない)
また、オブジェクト型といってもその実態は構造体へのポインタであり、型の宣言でもポインタ型であることを明示的に記述しなくてはならない(型の末尾又は変数の頭に'*'を付加する必要がある。
この辺、同じハイブリッド型の言語であるDelphiはオブジェクトがポインタ型であることを上手く隠蔽しており、殆どポインタを意識する必要が無いのと対照的だ。
中途半端なランタイム情報
Objective-Cは「ダイナミック」なC言語と呼ばれるが、その大きな理由の一つはランタイムにクラス、メッセージ、プロパティなどのメタデータを取得できることである。
具体的にはランタイムAPIを使用して、JavaやC#のリフレクションのようにクラスの情報からそのフィールド、プロパティ、メッセージ(メソッド)を取得できるのだが、この機能が私には中途半端に感じる。
一番困るのが、メッセージのシグネチャ、パラメタの静的な型がランタイムには判らないことである。 そんな馬鹿なと思うだろう?
- (void)hogeMessage:(id)target forName:(NSString *)name forVale:(MyValue *)value { 〜 }
このようなメソッド(メッセージ)があったとして、それぞれのパラメタ(forName:forValue)の型を調べるコードは以下のようになる。
NSMethodSignature* sig = [object methodSignatureForSelector:@selector(HogeMessage:forName:forValue)]; unsigned int argCount = [sig numberOfArguments]; for ( int index = 2 ; index < argCount; index++) { // argTypeにパラメタの型がエンコードされた文字がセットされる const char* argType = [sig getArgumentTypeAtIndex:index]; }
ここでargTypeに戻されるのは型をエンコードした文字であり、それは以下のように規定されている。
Objective-C Runtime Programming Guide: Type Encodings
c char
i int
s short
l long
q long long
C unsigned char
I unsigned int
S unsigned short
L unsigned long
Q unsigned long long
f float
d double
B C++のbool
v void
* 文字列(char*)
@ オブジェクト (静的に型定義されているまたはidとして型定義されているもの)
# クラスオブジェクト(Class)
: メソッドセレクタ(SEL)
[配列型] 配列
{名前=型...} 構造体
(型...) 共用体
bnum num ビットフィールド
^型 型へのポインタ
? 不明な型(関数ポインタ)スカラ型はエンコードされた文字から一意に型が決まるので良いのだが、問題はオブジェクト型である。
もう一度、オブジェクト型のエンコード文字を見てみよう。
@ オブジェクト (静的に型定義されているまたはidとして型定義されているもの)
つまりはどんな型定義がされていても全てのオブジェクト型は"@"一文字の情報しか持たない、ということである。
これは上記hogeMessageメッセージのシグネチャで記述されているパラメタ:forNameと:forValueそれぞれNSString*とMyValue*という静的な型で宣言されているが、ランタイム情報としてパラメタの型を調べて、NSString*型やMyValue*型かどうか確認する術が無いということである。
シグネチャに書かれているパラメタの型はコンパイル時の型チェックにしか使われていないようだ。(まるでイレイジャのようだ)
なお、同じようにエンコードされた文字で型を表しているプロパティは静的な型のランタイム情報を持っている。
プロパティのエンコードされた型情報の例
NSString*型のプロパティ -> T@"NSString"
MyValue*型のプロパティ -> "T@"MyValue"
これによって、プロパティの正しい型を特定できる。
どうしてメソッドもプロパティと同じ仕様にしなかったのだろうな。