2011年9月2日金曜日

メソッドに機能を追加したい場合

あまりに簡素だったので追記

まえがき

UIView などの既存のクラスにメソッドを追加するにはカテゴリを使うのが一般的(参考:http://news.mynavi.jp/column/objc/008/index.html)。 あるいは全く異なる機能を持たせたいならオーバーライドする。 では、既存のメソッドの機能を残しつつ、そこに独自の機能を追加したい場合はどうするか。一番スマートなのはサブクラスを作成して対処する方法。じゃあ、「サブクラス」なんか作りたくない、という時はどうするか。Delegate などで対処できないかよく考慮した上で、ダメなら自分で作ったメソッドと置き換えてしまおう。

runtime.h

runtime.h ヘッダーにはmethod_exchangeImplementationsというメソッドを置き換える関数が用意されている。 これとカテゴリによるメソッドの追加を組み合わせればメソッドに独自の機能を追加することができる。 - [UITableView reloadData] を reloadData2 で置き換えてみよう。まずは reloadData2 を用意する。
- (void)reloadData2
{
    [self reloadData2];
    SEL selector = @selector(tableViewDidEndReloadingData:);
    if( self.delegate && [self.delegate respondsToSelector:selector] ){
        [self.delegate performSelector:selector withObject:self];
    }
}
@end
ここで、reloadData2 の中で reloadData2 を呼び出していることに注意。メソッドが置き換わると - [UITableView reloadData] の呼び出しで上記の reloadData2 が呼び出される。この時、- [UITableView reloadData2] で呼び出されるのはオリジナルの reloadData だ。今回は元々の reloadData を呼び出した後に delegate に対して tableViewDidEndReloadingData: メソッドを呼び出すようにしている。 追加するメソッドが用意できたら次はいよいよ置き換えだ。
#import <objc/runtime.h>
static void UITableViewReplaceReloadDataMethod(void)
{
    Method original = class_getInstanceMethod([UITableView class], @selector(reloadData));
    Method replace = class_getInstanceMethod([UITableView class], @selector(reloadData2));
    method_exchangeImplementations(original, replace);
}
runtime.h ヘッダーをインポートを忘れないように注意する。コードはいたって簡単だ。これをアプリケーションのどこかで呼び出せば良いのだけど、注意は1度だけ呼び出すということ。なんども呼び出してしまうと、オリジナルの reloadData を呼び出したり、reloadData2 を呼び出したりしてしまう。 -[AppDelegate init] とか -[AppDelegate awakeFromNib] あたりが良いのではないかな。