既存クラスのメソッドを入れ替える(Method Swizzling)


状況：

* 画面が10個ほどあるiPhoneアプリ
* UIViewControllerのviewWillAppear等にログを仕込みたい。
* すでにUIViewControllerを継承したクラスが多数あって、全部にログを仕込むのはめんどくさいしダサくていやだ。

解決策：

* method_exchangeImplementationsでメソッドを入れ替えてしまえばいいよ！

黒魔法へようこそ
----
Objective-C Runtime には、[method_exchangeImplementations()](https://developer.apple.com/library/mac/documentation/cocoa/reference/objcruntimeref/Reference/reference.html)という関数が用意されています。これで「メソッドの交換」が可能となります。これはSDKで提供されているクラスのメソッドも入れ替える事が可能です。なのでUIViewControllerのviewWillAppear等を自作メソッドと差し替える事が出来ます。この差し替えは、動的に行われるので、動作する環境に応じて入れ替えをコントロールしたり、元に戻したりといった事が可能となります。

この方法は、"[Method Swizzling](https://www.google.co.jp/search?client=safari&rls=en&q=Method+Swizzling&ie=UTF-8&oe=UTF-8&gfe_rd=cr&ei=742yUsfLGeuT8QemqoHYCg)"と呼ばれるものみたいです。

以下のような実装をします。

* カテゴリ拡張で、入れ替えるメソッドを用意します。
  * このとき、「元々実装していたメソッド名」と「入れ替えた実装のメソッド名」も入れ替わるので、無限ループしそうなソースコードになります。
* class_getInstanceMethod()で、メソッドへの参照(Method)を取得します。
* method_exchangeImplementations()で、メソッドを入れ替えます。

サンプルコード
----
サンプルコードでは、UIViewControllerの``viewDidLoad``、``viewWillAppear``、``viewDidAppear``、``viewWillDisappear``、``viewDidDisappear``を、NSLog出力付きの自作メソッドに入れ替えています。

まずは、UIViewControllerをカテゴリ拡張して、入れ替えるメソッドの実装と、method_exchangeImplementations()を呼び出すクラスメソッドの実装を行います。

```objective-c:UIViewController+MethodSwitch.h
#import <UIKit/UIKit.h>
@interface UIViewController(MethodSwitch)
+(void)switchLoggingMethod;
@end
```

```objective-c:UIViewController+MethodSwitch.m
#import "UIViewController+MethodSwitch.h"
#import <objc/runtime.h>

@implementation UIViewController(MethodSwitch)

// 入れ替えるメソッドを準備
-(void)loggingViewDidLoad
{
    NSLog(@"%@ viewDidLoad",NSStringFromClass(self.class));
    [self loggingViewDidLoad];  // ここ、無限ループしそうだけど、メソッドの実装が入れ替わるので、元々あったviewDidLoadの実装が呼ばれます。
}
-(void)loggingViewWillAppear:(BOOL)animated{
    NSLog(@"%@ viewWillAppear",NSStringFromClass(self.class));
    [self loggingViewWillAppear:animated];
}
-(void)loggingViewDidAppear:(BOOL)animated{
    NSLog(@"%@ viewDidAppear",NSStringFromClass(self.class));
    [self loggingViewDidAppear:animated];
}
-(void)loggingViewWillDisappear:(BOOL)animated{
    NSLog(@"%@ viewWillDisappear",NSStringFromClass(self.class));
    [self loggingViewWillDisappear:animated];
}
-(void)loggingViewDidDisappear:(BOOL)animated{
    NSLog(@"%@ viewDidDisappear",NSStringFromClass(self.class));
    [self loggingViewDidDisappear:animated];
}

+(void)switchLoggingMethod
{
    // メソッドを入れ替える
    [self switchInstanceMethodFrom:@selector(viewDidLoad)        To:@selector(loggingViewDidLoad)       ];
    [self switchInstanceMethodFrom:@selector(viewWillAppear:)    To:@selector(loggingViewWillAppear:)   ];
    [self switchInstanceMethodFrom:@selector(viewDidAppear:)     To:@selector(loggingViewDidAppear:)    ];
    [self switchInstanceMethodFrom:@selector(viewWillDisappear:) To:@selector(loggingViewWillDisappear:)];
    [self switchInstanceMethodFrom:@selector(viewDidDisappear:)  To:@selector(loggingViewDidDisappear:) ];
}

+(void)switchInstanceMethodFrom:(SEL)from To:(SEL)to
{
    // メソッドの入れ替えの実態はここ
    Method fromMethod = class_getInstanceMethod(self,from);
    Method toMethod   = class_getInstanceMethod(self,to  );
    method_exchangeImplementations(fromMethod, toMethod);
}
@end
```

また、AppDelegate内で、[UIViewController switchLoggingMethod]を呼び出します。

```objective-c:AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    [UIViewController switchLoggingMethod];  // ←これでメソッドが入れ替わる
    return YES;
}
```

実行するとデバッグエリアにログが出力されます。
![MethodSwitch_xcodeproj_—_AppDelegate_m.png](https://qiita-image-store.s3.amazonaws.com/0/7946/b530ffe6-752d-12e3-64f6-c46a89c54426.png "MethodSwitch_xcodeproj_—_AppDelegate_m.png")

UIViewControllerそのものでも有効になりますし、それを継承したクラス(UINavigationController等）でも有効になります。

用量用法を守って、正しくお使いください。
----
このメソッドの入れ替えですが、普通にコードを読んで入れ替わってる事に気づくのは難しいです。知らない人がソースコードを読むと、「viewDidLoadに何も実装していないのにログが出力されるナンデ？！」と大混乱する事必至です。チームメンバーや未来の自分を混乱させないように、この辺りの情報はがっつり残しておきましょう。

でも、悪戯って楽しいよね。
----
例えば NSObject のメソッドを入れ替えちゃったりして。うへへへ

参考にしたサイト
----
[Safari用独自プラグインを作る(4) - Method Swizzling を試す](http://xcatsan.blogspot.jp/2009/10/safari4-method-swizzling.html)
[method_exchangeImplementationsでクラスメソッドを置換する](http://qiita.com/masarusanjp@github/items/fcb070880a8215a8cb4d)
[☆ モテる Objective-C 女子力を磨くための4つの心得「method swizzling できない女をアピールせよ」等](http://kita.dyndns.org/diary/?date=20110514) ←タイトルに負けた