Objective-Cのキャストはコンパイル警告を抑制するくらいの意味しかない

Objective-Cのクラスのキャストは少し緩くて、明らかにダウンキャストをしている場合でもコンパイル警告が出ません。

NSArrayはオブジェクトを追加できない

Objective-Cを書き始めたばかりの人が混乱しがちなのが、NSArrayとNSMutableArrayの変換だと思います。

NSArray *array = [NSArray arrayWithObjects:@"zero", @"one", @"two", nil];

NSArrayがあって、これに値を追加したい場合が出てきます。しかし、NSArrayはイミュータブルなクラスなので、値を追加することができません。

[array addObject:@"three"];

こういうことをしようとするとコンパイル警告が出てしまいます。
スクリーンショット 2013-08-12 23.22.53

だからってNSMutableArrayにキャストしてはいけない

NSMutableArrayならオブジェクトの追加ができるので、NSArrayから変換をしたいという願望が生まれます。そして「型の変換といえばキャスト」という考えで次のようなプログラムを書いてしまう場合があります。

NSMutableArray *mutableArray = (NSMutableArray *)array;
[mutableArray addObject:@"three"];

長らくObjective-Cを書いている人ですらこういう処理を書いているのを見たことがあるので、ちゃんと理解されていないのかもしれませんが、これは動きません。

-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x812f160
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x812f160'
*** First throw call stack:
(0x1c91012 0x10cee7e 0x1d1c4bd 0x1c80bbc 0x1c8094e 0x2b4c 0x10157 0x10747 0x1194b 0x22cb5 0x23beb 0x15698 0x1becdf9 0x1becad0 0x1c06bf5 0x1c06962 0x1c37bb6 0x1c36f44 0x1c36e1b 0x1117a 0x12ffc 0x27d2 0x2705)
libc++abi.dylib: terminate called throwing an exception

__NSArrayIという見慣れないシンボルがありますが、これはNSArrayがクラスタクラスという特殊な構造になっているためにこうなっているだけで、実体はNSArrayだと思って問題ないです。
つまり、NSMutableArrayにキャストしたところで実体はNSArrayなので、addObject:というメソッドは存在せず、エラーになってしまうのです。

明らかにダウンキャストなのに警告は出ない

そもそもこれは明らかにダウンキャストで、キャストする時点で安全性が保証されないわけなのですが、Xcodeはコンパイル警告を出してくれません。
Javaでこんなことをしようと思うと、コンパイルエラーとなりますし、実際にキャストを実行しようとするとClassCastExceptionの例外が発生します。一方、Objective-Cはコンパイル警告も出さないし、キャストも何事も無く通過してしまいます。実際にメソッドを呼び出した時にはじめてメソッドが存在しないことを知ります。

NSStringだってNSMutableArrayにキャストできる

最後にとても不可解なソースコードを載せて終わりにします。

NSString *string = @"string";
NSMutableArray *mutableArray = (NSMutableArray *)string;
[mutableArray addObject:@"three"];

NSStringをNSMutableArrayにキャストしてオブジェクトを追加しようとしています。
キャストできるわけがないと思うわけですが、これもコンパイル警告は出ませんし、キャストも通過し、addObject:を呼ぼうとしてはじめてエラーとなります。不用意にダウンキャストをしてしまうと、実行時にしか問題が発覚しないため、デバッグするのにものすごく労力を使います。
そういうわけで、Objective-Cのクラスのキャストは、乱暴に言ってしまえばコンパイル警告を抑制するくらいの機能しかないので、基本的に使わないほうが良いと思います。

タイトルとURLをコピーしました