最近、Effective Objective-C 2.0を読んでいます。
色々オススメされる書籍を読めば読むほど、C言語について理解する必要があるなーと思わされます。
今回は、Swizzling
を使ったDebug方法です。
アスペクト指向とか、そのあたりがキーワードですね。
JavaScriptでも比較的簡単に実装することが出来ますが、Objective-Cでもランタイムを使うことによって実装が可能です。
ざっくり手順。
- とある実装(メソッド)を置き換えるメソッドを準備する
- 実行時(ランタイム)にメソッドを入れ替える
- 置き換えるメソッド内で元々のメソッドも実行する
- NSStringなどの中が分からないメソッドに対しても機能拡張できる(ログ出力とか)
ということで、実際にNSString
のlowwercaseString
メソッドにログ出力機能を付けるサンプルです。
##置き換える実装を準備する
置き換える実装(メソッド)の準備はカテゴリを使います。
@interface NSString (sample)
- (NSString *)swapLowercaseString;
@end
////////////////////////////////////////
@implementation NSString (sample)
- (NSString *)swapLowercaseString
{
NSString *lowercase = [self swapLowercaseString];
NSLog(@"Called. %@", lowercase);
return lowercase;
}
@end
###差し替え部分
ここが要の部分ですね。
objc/runtime.h
が必要です。
見た通り、ランタイム用の関数が定義されています。
#import <objc/runtime.h>
#import "NSString+sample.h"
int main(int argc, char * argv[])
{
// NSString#lowercaseStringの実装を取り出す
Method lowercaseString = class_getInstanceMethod([NSString class], @selector(lowercaseString));
// カテゴリで追加した`swapLowercaseString`の実装を取り出す
Method swapLowercaseString = class_getInstanceMethod([NSString class], @selector(swapLowercaseString));
// 取り出した実装を入れ替える
method_exchangeImplementations(lowercaseString, swapLowercaseString);
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([SampleAppDelegate class]));
}
}
###使用部分
NSString *sample1 = @"This is Sample";
NSString *sample2 = [sample1 lowercaseString]; // => "Called. this is sample"
NSLog(@"%@", sample2); // => "this is sample"
##簡単に解説
セレクタという名前から想像するに、なにかを選択しているわけですが、なにを選択しているかというと「実装を選択」しているわけです。
だから「セレクタ」っていうわけですね。
Objective-Cはコンパイル後にはCとして動作します。
当然、Cにはクラスという概念がありません。(オブジェクト指向言語ではない)
なので、ランタイムはObjecitve-Cで定義したインスタンスメソッドなどをうまく利用できる仕組みを提供してくれています。
それがセレクタと実装です。これらの対応表をテーブルにまとめておき、実行時にそれをルックアップして実行を決める、というのがざっくりとした説明です。
そして今回のサンプルではこの「実行時のルックアップ」を操作して、セレクタが指し示す実装を切り替えている、というわけです。