我可以拦截 Objective-C 中的方法调用吗?如何?
编辑: Mark Powell的回答给了我一个部分解决方案,即-forwardInvocation方法。但是文档指出 -forwardInvocation 仅在向对象发送没有相应方法的消息时才调用。我希望在所有情况下都可以调用一个方法,即使接收者确实有那个选择器。
我可以拦截 Objective-C 中的方法调用吗?如何?
编辑: Mark Powell的回答给了我一个部分解决方案,即-forwardInvocation方法。但是文档指出 -forwardInvocation 仅在向对象发送没有相应方法的消息时才调用。我希望在所有情况下都可以调用一个方法,即使接收者确实有那个选择器。
你可以通过调配方法调用来做到这一点。假设你想获取 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 运行时文档中获得更多详细信息。
在 Objective-C 中发送消息被转换为对函数objc_msgSend(receiver, selector, arguments)
或其变体之一的调用objc_msgSendSuper
, objc_msgSend_stret
, objc_msgSendSuper_stret
.
如果可以更改这些函数的实现,我们可以拦截任何消息。不幸的是,objc_msgSend
它是 Objective-C 运行时的一部分,不能被覆盖。
通过谷歌搜索,我在 Google Books: A Reflective Architecture for Process Control Applications by Charlotte Pii Lunau上找到了一篇论文。该论文通过将对象的isa
类指针重定向到自定义 MetaObject 类的实例来介绍一种 hack。因此,打算发送给修改对象的消息将发送到 MetaObject 实例。由于 MetaObject 类没有自己的方法,因此它可以通过将消息转发到修改后的对象来响应转发调用。
该论文不包括源代码中有趣的部分,我不知道这种方法是否会对 Cocoa 产生副作用。但尝试一下可能会很有趣。
如果要记录从应用程序代码发送的消息,-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;
您将有一个消息发送日志。请注意,从被拦截对象内部发送的发送不会被拦截,因为它们将使用原始对象“self”指针发送。
如果您只想记录从外部调用的消息(通常从委托中调用;查看哪种消息、何时等),您可以像这样覆盖 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;
}
创建一个子类NSProxy
并实现-forwardInvocation:
and -methodSignatureForSelector:
(或者-forwardingTargetForSelector:
,如果您只是将它指向第二个对象而不是自己摆弄该方法)。
NSProxy
是一个为实现而设计的类-forwardInvocation:
。它有一些方法,但大多数情况下你不希望它们被抓住。例如,捕获引用计数方法将阻止代理被释放,除非在垃圾回收下。但是,如果NSProxy
您绝对需要转发某些特定方法,则可以专门覆盖该方法并-forwardInvocation:
手动调用。请注意,仅仅因为NSProxy
文档下列出了一个方法并不意味着NSProxy
实现它,只是希望所有代理对象都有它。
如果这对您不起作用,请提供有关您的情况的更多详细信息。
也许你想要NSObject
的-forwardInvocation
方法。这使您可以捕获消息,重新定位它,然后重新发送它。
您可以使用您自己的方法调用来调整方法调用,它可以在“拦截”上执行您想要执行的任何操作并调用原始实现。Swizzling 是通过 class_replaceMethod() 完成的。
方法调用,不。发送一条消息,是的,但如果你想要一个关于如何发送的好答案,你将不得不更具描述性。
要在调用方法时执行某些操作,您可以尝试基于事件的方法。因此,当调用该方法时,它会广播一个事件,该事件被任何侦听器拾取。我对目标 C 不是很好,但我只是在 Cocoa 中使用NSNotificationCenter找到了类似的东西。
但是,如果“拦截”的意思是“停止”,那么也许您需要更多的逻辑来决定是否应该调用该方法。