UICollectionViewCellのサブクラスをiOS 5で共存させる
- 2012年 10月14日
iOS 6ではUICollectionViewってのが追加された。テーブルビューを超えるとっても便利なクラス。でも、これを使ったアプリをiOS 5でも動作させようとしたら、ちょっとした問題が。
新機能を使ったアプリを過去のバージョンのOSで動作させる場合、いくつかのテクニックがある。新規フレームワークはweakリンクするとか。新規クラスは直接記述せずNSClassFromStringで取得するとか。そうやって一個ずつクリアしていったんだけど、これどうしよう?っていう問題にぶつかった。
UICollectionViewでは、UICollectionViewCellっていうセルクラスを使う。画面に表示する内容をカスタマイズするもので、テーブルビューのセルみたいなやつだ。UICollectionViewCellのサブクラスを作るってのが、普通の使い方になる。
これが問題に。サブクラスを作るということは、ビルド時に親クラスを指定してやらないといけない。指定された親クラスは、バイナリファイルに解決が必要なクラスとして記述されて、起動時にdyldが解決しようとする。このとき、UICollectionViewCellクラスが存在しないと、起動に失敗する。従って、バイナリにUICollectionViewのサブクラスが含まれていると、そのクラスを実行時にインスタンス化するしないに関わらず、iOS 5での起動ができない。
うーむ、困った。どうしよう?やっぱりUICollectionViewを使うのをやめるとか、iOS 5は対応しないとか、後ろ向きな解決策も考えた。または、iOS 5でも動作する互換クラスを使うとか(PSTCollectionViewみたいな)。けど、釈然としない。おれは、iOS 6ではUICollectionViewを使って、iOS 5では使わないっていう、そういうバイナリを作りたいんだよ。
いろいろ悩みまくったあげく、トリッキーな解決策にたどり着いた。要は、UICollectionViewCellのサブクラスが起動時に存在するから悪いのだ。ならば、始めはUIViewのサブクラスにでもしておけばいい。そして、起動した後で、その親クラスを差し替えればいいのだ。
「親クラスを差し替える?」なんだそりゃ、と思われたそこのあなた。Objective-Cではそんなことだってできてしまいます。だって、変態言語だもの。
Objective-CのランタイムAPIには、class_setSuperclassというのがある。文字通り、親クラスを設定するためのAPIだ。これを使えば、あるクラスの親クラスを動的に変更することができる。もちろん、下手にこんなことをすればとんでもないことになるので、使用には細心の注意が必要だ。ドキュメントには一言、「You should not use this function.」とだけ書いてある。そんなら、こんなAPI用意しておくなよ。
実験してみたloadメソッドに次のように書く。
+ (void)load { // UICollectionViewCellのクラスを取得 Class collectionViewCellClass; collectionViewCellClass = NSClassFromString(@"UICollectionViewCell"); // 存在する場合 if (collectionViewCellClass) { // 親クラスを差し替える Class magazineCollectionCellClass; magazineCollectionCellClass = NSClassFromString(@"HJMagazineCollectionCell"); class_setSuperclass(magazineCollectionCellClass, collectionViewCellClass); } }
これを使えば意図通りの結果を得ることができた。とっても気持ち悪いが、とりあえずこれでしのげるかどうか、しばらく試してみる。しかし、Objective-Cフリーダムすぎる。