Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

状況:

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

解決策:

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

黒魔法へようこそ

Objective-C Runtime には、method_exchangeImplementations()という関数が用意されています。これで「メソッドの交換」が可能となります。これはSDKで提供されているクラスのメソッドも入れ替える事が可能です。なのでUIViewControllerのviewWillAppear等を自作メソッドと差し替える事が出来ます。この差し替えは、動的に行われるので、動作する環境に応じて入れ替えをコントロールしたり、元に戻したりといった事が可能となります。

この方法は、"Method Swizzling"と呼ばれるものみたいです。

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

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

サンプルコード

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

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

UIViewController+MethodSwitch.h
#import <UIKit/UIKit.h>
@interface UIViewController(MethodSwitch)
+(void)switchLoggingMethod;
@end
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]を呼び出します。

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

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

用量用法を守って、正しくお使いください。

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

でも、悪戯って楽しいよね。

例えば NSObject のメソッドを入れ替えちゃったりして。うへへへ

参考にしたサイト

Safari用独自プラグインを作る(4) - Method Swizzling を試す
method_exchangeImplementationsでクラスメソッドを置換する
☆ モテる Objective-C 女子力を磨くための4つの心得「method swizzling できない女をアピールせよ」等 ←タイトルに負けた

paming
Android修行中
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away