自作のSDK・ライブラリを組み込んでテスト実装をしている時にどうしても非公開クラスの非公開メソッドを叩きたい、DBデータを参照したい。という時に使ったり使わなかったり。
いわゆるObjective-Cの黒魔術。
今回は黒魔術でも入門向けな黒魔術。
黒魔術の概要
どれも以下のインポートが必要です。
# import <objc/runtime>
非公開なクラスオブジェクトを取得する
id classObject = objc_getClass("MyLibraryDataManager");
もう一つのアプローチ
コメントにて頂いた方法です。こちらはimportが必要ありません。
id classObject = NSClassFromString(@"MyLibraryDataManager");
メソッドを実行する
// クラスメソッド.
[[classObject class] performSelector:@selector(someMethod)];
// インスタンスの取得
id instance = [[classObject class] performSelector:@selector(new)];
// インスタンスメソッドの実行
id retValue = [instance performSelector:@selector(instanceMethod)];
クラス名は知っている前提、メソッド名も知っている前提でのみ動きます。
実用編
非公開なDB管理クラスのコンテキストを取得して、格納されているデータの数が知りたい。みたいな状況。実用編と書きつつ、実用的でない限定的な話。
// undeclared-selectorのワーニングを出さないようにする
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wundeclared-selector"
// クラスを取得し、シングルトンなインスタンスを取得.
id managerClass = objc_getClass("MyLibraryDBManager");
id managerInstance = [[managerClass class] performSelector:@selector(sharedManager)];
// プライベートに実装されているmanagedContextメソッドを実行し、contextを取得
NSManagedObjectContext* context = [managerInstance performSelector:@selector(managedContext)];
// warning無視を終了
# pragma clang diagnostic pop
// 以下CoreData fetch
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"MyDBEntityName" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
// 件数を取得
NSInteger count = fetchedObjects.count;
上記の実装は以下を知っていること前提です。
- 非公開なクラスの名前
- このクラスがsharedManagerというクラスメソッドでシングルトンインスタンスを取得できる
- managedContextというプライベートなメソッドが色々やっている
- DBのエンティティ名
- これらのインターフェースには変更がない。または変更を即時反映できる。
と、かなり限定的です。使う機会はないでしょう。
注意点
今回は、自分が知っているライブラリのデータを参照しました。
接続先サーバーのURLを生成するAPIを知っていて、それを実装側で確認したい。また、CoreDataに格納されているデータ数を公開APIにはない方法で取得したい。等です。
これくらいなら使っても良いと思います。
ただし、自分が開発中のプロジェクトでこのようにデータを参照しなければならなくなった場合、これは設計ミスです。
必要なAPIが実装されていない上に、それを変更できずに無理やり参照する必要があるのは異常です。
何が問題かというと、変更に耐えられないことです。
今回実装したものは全て決め打ちで文字列を打ち込んで実行しています。もちろんメソッド名が変更されたらクラッシュします。
しかもコンパイル時点では気づきません。リリース後なんて目も当てられません。
まとめ
黒魔術は入門編であっても、黒魔術です。
用途はデバッグ、または単体テストにとどめましょう。