19

Objective-C でメソッド呼び出しをインターセプトできますか? どのように?

編集: Mark Powellの回答により、部分的な解決策である-forwardInvocationメソッドが得られました。ただし、ドキュメントには、対応するメソッドがないオブジェクトがメッセージを送信された場合にのみ、 -forwardInvocation が呼び出されると記載されています。レシーバーにそのセレクターがある場合でも、すべての状況でメソッドが呼び出されるようにしたいと思います。

4

10 に答える 10

24

メソッド呼び出しをスウィズルすることでそれを行います。すべてのリリースを NSTableView に取得するとします。

static IMP gOriginalRelease = nil;
static void newNSTableViewRelease(id self, SEL releaseSelector, ...) {
   NSLog(@"Release called on an NSTableView");
   gOriginalRelease(self, releaseSelector);
}


  //Then somewhere do this:
  gOriginalRelease = class_replaceMethod([NSTableView class], @selector(release), newNSTableViewRelease, "v@:");

詳細については、Objective C ランタイムドキュメントを参照してください。

于 2009-10-29T09:35:39.040 に答える
15

Objective-C でのメソッド呼び出しのインターセプト (C 呼び出しではなく、Objective-C であると仮定) は、メソッド スウィズリングと呼ばれる手法で行われます。

これを実装する方法については、こちらで紹介しています。実際のプロジェクトでメソッド スウィズリングを実装する方法の例については、OCMock (Objective-C の分離フレームワーク) を参照してください。

于 2009-10-24T17:15:11.920 に答える
11

objc_msgSend(receiver, selector, arguments)Objective-C でのメッセージの送信は、関数またはそのバリアントobjc_msgSendSuperの呼び出しobjc_msgSend_stretに変換されますobjc_msgSendSuper_stret

これらの関数の実装を変更できれば、あらゆるメッセージを傍受できます。残念ながら、objc_msgSendこれは Objective-C ランタイムの一部であり、オーバーライドできません。

Google ブックスで論文を見つけました: A Reflective Architecture for Process Control Applications by Charlotte Pii Lunau。この論文では、オブジェクトのisaクラス ポインタをカスタム MetaObject クラスのインスタンスにリダイレクトするというハックが紹介されています。したがって、変更されたオブジェクトを対象としたメッセージは、MetaObject インスタンスに送信されます。MetaObject クラスには独自のメソッドがないため、メッセージを変更されたオブジェクトに転送することで、転送呼び出しに応答できます。

この論文にはソースコードの興味深い部分は含まれておらず、そのようなアプローチが Cocoa に副作用をもたらすかどうかはわかりません。でもやってみるのも面白いかも。

于 2009-10-27T16:26:54.240 に答える
5

アプリケーション コードから送信されたメッセージをログに記録する場合は、-forwardingTargetForSelector: ヒントが解決策の一部です。
オブジェクトをラップします。

@interface Interceptor : NSObject
@property (nonatomic, retain) id interceptedTarget;
@end

@implementation Interceptor
@synthesize interceptedTarget=_interceptedTarget;

- (void)dealloc {
    [_interceptedTarget release];
    [super dealloc];
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"Intercepting %@", NSStringFromSelector(aSelector));
    return self.interceptedTarget;
} 

@end

次のようにします。

Interceptor *i = [[[Interceptor alloc] init] autorelease];
NSFetchedResultsController *controller = [self setupFetchedResultsController];
i.interceptedTarget = controller;
controller = (NSFetchedResultsController *)i;

メッセージ送信のログが表示されます。傍受されたオブジェクト内から送信された送信は、元のオブジェクトの「自己」ポインターを使用して送信されるため、傍受されないことに注意してください。

于 2011-08-20T06:53:02.480 に答える
3

外部から呼び出されたメッセージのみをログに記録したい場合 (通常はデリゲートから呼び出され、どの種類のメッセージをいつ確認するかなど)、次のように RespondsToSelector をオーバーライドできます。

- (BOOL)respondsToSelector:(SEL)aSelector {
NSLog(@"respondsToSelector called for '%@'", NSStringFromSelector(aSelector));

// look up, if a method is implemented
if([[self class] instancesRespondToSelector:aSelector]) return YES;

return NO;

}

于 2012-01-19T11:56:18.670 に答える
2

のサブクラスを作成してNSProxyand を実装-forwardInvocation:します-methodSignatureForSelector:(または-forwardingTargetForSelector:、自分でメソッドをいじるのではなく、単純に 2 番目のオブジェクトに指示する場合)。

NSProxyに実装するために設計されたクラスです-forwardInvocation:。いくつかの方法がありますが、ほとんどの場合、それらをキャッチしたくありません。たとえば、参照カウント メソッドをキャッチすると、ガベージ コレクション以外でプロキシの割り当てが解除されるのを防ぐことができます。ただし、NSProxy絶対に転送する必要がある特定のメソッドがある場合は、そのメソッドを具体的にオーバーライドして-forwardInvocation:手動で呼び出すことができます。NSProxyメソッドがドキュメントの下にリストされているからといって、メソッドがそれを実装していることを意味するわけではなくNSProxy、すべてのプロキシ オブジェクトがそれを持っていることが期待されているだけであることに注意してください。

これがうまくいかない場合は、状況に関する追加の詳細を提供してください。

于 2009-10-30T15:50:24.527 に答える
1

NSObjectのメソッドが必要な場合があります-forwardInvocation。これにより、メッセージをキャプチャし、ターゲットを変更してから再送信できます。

于 2009-10-24T16:54:45.833 に答える
1

メソッド呼び出しを独自のものでスウィズルできます。これは、「インターセプト」でやりたいことを何でも実行し、元の実装を呼び出します。スウィズリングは class_replaceMethod() で行われます。

于 2009-10-24T17:06:22.627 に答える
0

メソッド呼び出し、いいえ。メッセージの送信、はい。

于 2009-10-24T16:57:27.473 に答える
-1

メソッドが呼び出されたときに何かを行うには、イベント ベースのアプローチを試すことができます。そのため、メソッドが呼び出されると、リスナーによってピックアップされるイベントがブロードキャストされます。私はオブジェクティブ C は得意ではありませんが、Cocoa でNSNotificationCenterを使用して似たようなことを見つけました。

ただし、「インターセプト」が「停止」を意味する場合は、メソッドを呼び出す必要があるかどうかを判断するために、さらにロジックが必要になる可能性があります。

于 2009-10-24T16:43:04.977 に答える