背景
UIStoryboardは下記のファクトリを使って生成します。
+ (UIStoryboard *)storyboardWithName:(NSString *)name bundle:(NSBundle *)storyboardBundleOrNil
今回はTyphoon FrameworkというDIコンテナを導入するにあたって、このファクトリで生成されるUIStoryboardをサブクラスで置き換える必要が出て来ました。
ClassメソッドのSwizzling
UIStoryboardのクラスメソッドの入れ替えは、Objective Cのランタイム機能を使うと簡単にできます。
- (void)swizzleSample
{
Method original,swizzled;
original = class_getClassMethod([UIStoryboard class], @selector(storyboardWithName:bundle:));
swizzled = class_getClassMethod([UIStoryboard class], @selector(ppStoryboardWithName:bundle:));
method_exchangeImplementations(original, swizzled);
}
ただ、ここで問題になるのがサブクラス側でのファクトリ実装です。
今回組み込みたいサブクラスは下記のようにスーパークラスのファクトリメソッドを使用しています。というか、UIStoryboardの場合にはこうせざるを得ないです。
TyphoonStoryboard.h
+ (TyphoonStoryboard *)storyboardWithName:(NSString *)name bundle:(NSBundle *)storyboardBundleOrNil
{
return [super storyboardWithName:name bundle:storyboardBundleOrNil];
}
単純にファクトリメソッドを入れ替えると無限ループしてしまうため、下記のようにselfに格納されたクラスオブジェクトによって処理を分岐するようにします。
@implementation UIStoryboard (PPTyphoon)
+ (UIStoryboard *)ppStoryboardWithName:(NSString *)name bundle:(NSBundle *)storyboardBundleOrNil
{
if(self != [UIStoryboard class]){
// Called by subclass. Use original implementation
return [self ppStoryboardWithName:name bundle:storyboardBundleOrNil];
}
return [TyphoonStoryboard storyboardWithName:name
factory:[TyphoonComponentFactory defaultFactory]
bundle:storyboardBundleOrNil];
}
@end
これにより、下記のような順序で処理が実行され、無事に生成されるクラスをサブクラスで置き換えることができました。
- UIStoryBoardのファクトリ呼び出し
- UIStoryboardの独自ファクトリ(swizzled)-> TyphoonStoryboard呼び出し
- TyphoonStoryboardのファクトリ -> super呼び出し
- UIStoryboardの独自ファクトリ(swizzled) -> 本来の実装呼び出し
- UIStoryboardの本来のファクトリ
Objective Cって 気持ち悪い 面白いですね。
Typhoonって何?
DIコンテナです。Typhoon Frameworkについてはまた別の機会に。