【发布时间】:2010-12-09 18:17:05
【问题描述】:
我可以在 Objective-C 中拦截方法调用吗?怎么样?
编辑: Mark Powell 的回答给了我一个部分解决方案,-forwardInvocation 方法。 但是文档指出 -forwardInvocation 仅在向对象发送没有相应方法的消息时才调用。我想要一个在所有情况下都可以调用的方法,即使接收者确实有那个选择器。
【问题讨论】:
标签: objective-c methods intercept
我可以在 Objective-C 中拦截方法调用吗?怎么样?
编辑: Mark Powell 的回答给了我一个部分解决方案,-forwardInvocation 方法。 但是文档指出 -forwardInvocation 仅在向对象发送没有相应方法的消息时才调用。我想要一个在所有情况下都可以调用的方法,即使接收者确实有那个选择器。
【问题讨论】:
标签: objective-c methods intercept
您可以通过调配方法调用来做到这一点。假设你想获取 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 运行时documentation 中获取更多详细信息。
【讨论】:
在 Objective-C 中发送消息被转换为对函数 objc_msgSend(receiver, selector, arguments) 或其变体 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 类的实例来介绍一种 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;
您将有一个消息发送日志。请注意,从被拦截对象内部发送的发送不会被拦截,因为它们将使用原始对象的“自我”指针发送。
【讨论】:
如果您只想记录从外部调用的消息(通常从委托中调用;查看哪种消息、何时等),您可以像这样覆盖 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: 和-methodSignatureForSelector:(或-forwardingTargetForSelector:,如果您只是将其指向第二个对象而不是自己摆弄方法)。
NSProxy 是一个为实现-forwardInvocation: 而设计的类。它有一些方法,但大多数情况下你不希望它们被抓住。例如,捕获引用计数方法将阻止代理被释放,除非在垃圾收集下。但如果NSProxy 上有特定方法绝对需要转发,则可以专门覆盖该方法并手动调用-forwardInvocation:。请注意,NSProxy 文档下列出的方法并不意味着 NSProxy 实现了它,而只是希望所有代理对象都有它。
如果这对您不起作用,请提供有关您的情况的更多详细信息。
【讨论】:
也许你想要NSObject 的-forwardInvocation 方法。这使您可以捕获一条消息,重新定位它,然后重新发送它。
【讨论】:
您可以使用您自己的方法调用来调整方法调用,它会在“拦截”时执行您想要执行的任何操作并调用原始实现。 Swizzling 是通过 class_replaceMethod() 完成的。
【讨论】:
一个方法调用,不。发送一条消息,是的,但如果你想要一个关于如何发送的好答案,你将不得不更具描述性。
【讨论】:
要在调用方法时执行某些操作,您可以尝试基于事件的方法。因此,当调用该方法时,它会广播一个事件,该事件被任何侦听器接收。我对目标 C 不是很好,但我只是在 Cocoa 中使用 NSNotificationCenter 找到了类似的东西。
但是如果“拦截”是指“停止”,那么您可能需要更多逻辑来决定是否应该调用该方法。
【讨论】: