【iOS/Objective-c】Method Swizzling

追記しました 2014/12/29

【iOS/Objective-c】Method Swizzling 修正版 - ふるすたっくえんじにあっぽい人の日記


今日はMethod Swizzlingだぉ
いわゆるメソッドのフックだぉ
世間では黒魔術と呼ばれてるぉ
これとかカテゴリとかがあるから、なおさらObjective-cでライブラリとか使いたくないんだぉ

ググると +(void)load内でmethod_exchangeImplementationsしてる人がいるようですがね
loadのタイミングだと[UIApplication sharedApplication]がnilですがね
いや、ほんとはわたくしもloadに書きたいんですがね

今回晒すのはAppDelegateで呼ばれるメソッドたちをフックする例
拡張性もなにもいらないんで、適当
AppDelegateのdidFinishLaunchingWithOptions内で[Hoge sharedInstance]を呼び出してもらえれば

swizzled AppDelegate Methodsの下を見るとどーみても無限ループにしか見えないんだぉ
実際はexchangeされてるから、AppDelegateのメソッドが呼ばれるんだぉ
ちなみにもちろんこのswizlled Method内ではselfはAppDelegateになるから要注意だぉ
ここらへんが黒魔術と呼ばれる所以だぉ
カオスになるからお子ちゃまはMethod Swizzling使っちゃだめだぉ


#pragma mark - Initialize
+ (Hoge *)sharedInstance {
    static Hoge *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[Hoge alloc] initInstance];
    });
    return instance;
}

- (id)init {
    [self doesNotRecognizeSelector:_cmd];
    return nil;
}

- (id)initInstance {
    self = [super init];
    if (self) {
        [self swizzleAppDelegateMethods];
    }
    return self;
}

#pragma mark - Private Methods
/*! @brief AppDelegateのメソッドを自クラスのメソッドと差し替え */
- (void)swizzleAppDelegateMethods {
    [self swizzleMethod:@selector(applicationWillResignActive:)];
    [self swizzleMethod:@selector(applicationDidEnterBackground:)];
    [self swizzleMethod:@selector(applicationWillEnterForeground:)];
    [self swizzleMethod:@selector(applicationDidBecomeActive:)];
    [self swizzleMethod:@selector(applicationWillTerminate:)];
    [self swizzleMethod:@selector(application:didReceiveRemoteNotification:)];
}

- (void)swizzleMethod:(SEL)sel {
    Method originalMethod = class_getInstanceMethod([[UIApplication sharedApplication].delegate class], sel);
    Method altMethod = class_getInstanceMethod([self class], sel);
    method_exchangeImplementations(originalMethod, altMethod);
}

#pragma mark - swizzled AppDelegate Methods
- (void)applicationWillResignActive:(UIApplication *)application {
    [[Hoge sharedInstance] applicationWillResignActive:application];
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    [[Hoge sharedInstance] applicationDidEnterBackground:application];
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    [[Hoge sharedInstance] applicationWillEnterForeground:application];
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    [[Hoge sharedInstance] applicationDidBecomeActive:application];
}

- (void)applicationWillTerminate:(UIApplication *)application {
    [[Hoge sharedInstance] applicationWillTerminate:application];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    [[Hoge sharedInstance] application:application didReceiveRemoteNotification:userInfo];
}