ドッグフード
エラー内容からARCが原因であることは間違い無い。引数の型がvoid**であることから、パラメタのオーナシップの移動に関してであろうことは想像に難くないが、「何故」駄目なのかをきちんと知りたいところ。
ランタイムAPIの一つ、インスタンスフィールド※を取得するobject_getInstanceVariableがARCで使えないのはどうしてか、それを調べるにはソースコードを調べるしかない。しかしXcodeを含めてiOS SDKにソースコードは提供されておらず、そもそもオープンでは無いシステムでソースコードなど期待できないと思っていたのだが、そうでもないらしい。
Appleのオープンソースサイトではシステムの主要なソースコードが公開されており、勿論Objective-CのランタイムAPIの実態も見ることができる。
objc4-493.11 - Mac OS X 10.7.3 Source
これははっきりいって宝の山だ。どんなドキュメントよりもためになる。Objective-CランタイムはAPSLライセンス(Apple Public Source License)の元公開されており、誰でも同ライセンス下で使用できる。
早速問題の関数を見てみよう。object_getInstanceVariableはobjc-class.mに実装がある。
objc-class.m
Ivar object_getInstanceVariable(id obj, const char *name, void **value) { if (obj && name) { Ivar ivar; if ((ivar = class_getInstanceVariable(_object_getClass(obj), name))) { if (value) *value = (void *)object_getIvar(obj, ivar); return ivar; } } if (value) *value = NULL; return NULL; }
同関数はたったこれだけの実装だが、これで使えない理由が分った。
ARC(Automatic Reference Counting)の管理対象となっているオブジェクトはid型(オブジェクト型)<->void*型相互のキャストはARCがライフサイクルを管理できないため禁止されておりコンパイル時にチェックがかかる。(ARCが無効の状態ではなんの警告も出ずにコンパイル、実行が可能)
この関数で言うと、object_getIvarの戻り値を(void *)にキャストしている部分がもろこの制限に引っ掛かる。この制限を回避するための方法はちゃんと用意されているが、ソースコードをビルドする訳にはいかないため、この関数をARC対応に修正したものを用意してそれを呼び出すことで対処するのがよさそうだ。
ARC下で上記のキャストを行う方法だが、以下の3つが用意されている
__bridge
キャストを実行するが、どちらの参照カウントも変更されない
__bridge_retained
キャストを実行して、キャスト先にretainを実行する(キャスト先の参照カウントの1加算する)
__bridge_transfer
キャストを実行して、キャスト先にオーナシップを移行する(キャスト元の参照カウントを1減じる)
これらは指定を間違うと一発でEXC_BAD_ACCESSやメモリリークを発生させる為、慎重に指定する必要がある。
※Objective-Cの世界では Instance Variableのアクロニムから"ivar"と略することが多い