Objective-Cでクラスメソッドを置換するやり方について忘れがちなのでメモ
テストコードを書いてるときなどに、テスト対象のコードの中で呼んでいるクラスメソッドをモックに差し替えたい場合があります。
本来であればその手の依存性はうまい事排除してあげるのが良いのですが、いかんともしがたい場合ですね。
Objective-Cはruntimeに触る事が出来るので割りとサクッとできます。
以下のクラスのメソッドを入れ替えてみましょう。
@interface GlobalClass : NSObject
+ (BOOL)classMethod;
@end
適当なカテゴリで入れ替えたいメソッドと同じ定義のクラスメソッドをそのクラスに生やします。
#import <objc/runtime.h>
@interface SomelClass(TestCode)
+ (BOOL)replacing_classMethod;
@end
@implementation SomeClass(TestCode)
+ (BOOL)replacing_classMethod {
// iroiro suruyo
}
@end
method_exchangeImplementationsで実装を入れ替えてあげます。
メソッドを入れ替えるコードはこちら
@interface SomeTestClass : GHTestCase {
Method oldMethod;
Method newMethod;
}
@end
@implementation SomeTestClass
- (void)setUp {
oldMethod = class_getClassMethod([SomeClass class], @selector(classMethod));
newMethod = class_getClassMethod([SomeClass class], @selector(replacing_classMethod));
method_exchangeImplementations(oldMethod, newMethod);
}
@end
これで入れ替え完了です。
method_exchangeImplementationsで何が起こっているか、前後で値を見てみると、implementationの指す先が変わっているのがわかります。
Method oldMethod = class_getClassMethod([SomeClass class], @selector(classMethod));
Method newMethod = class_getClassMethod([SomeClass class], @selector(replacing_classMethod));
NSLog(@"oldMethod name is %@, imp is %x", NSStringFromSelector(method_getName(oldMethod)), (NSInteger)method_getImplementation(oldMethod));
NSLog(@"newMethod name is %@, imp is %x", NSStringFromSelector(method_getName(newMethod)), (NSInteger)method_getImplementation(newMethod));
method_exchangeImplementations(oldMethod, newMethod);
NSLog(@"oldMethod name is %@, imp is %x", NSStringFromSelector(method_getName(oldMethod)), (NSInteger)method_getImplementation(oldMethod));
NSLog(@"newMethod name is %@, imp is %x", NSStringFromSelector(method_getName(newMethod)), (NSInteger)method_getImplementation(newMethod));
oldMethod name is classMethod, imp is 375d9
newMethod name is replacing_classMethod, imp is 375f5
oldMethod name is classMethod, imp is 375f5
newMethod name is replacing_classMethod, imp is 375d9
以上です。
黒魔法は用法用量を守って楽しく使おう