【问题标题】:Intercept method call in Objective-CObjective-C 中的拦截方法调用
【发布时间】:2010-12-09 18:17:05
【问题描述】:

我可以在 Objective-C 中拦截方法调用吗?怎么样?

编辑: Mark Powell 的回答给了我一个部分解决方案,-forwardInvocation 方法。 但是文档指出 -forwardInvocation 仅在向对象发送没有相应方法的消息时才调用。我想要一个在所有情况下都可以调用的方法,即使接收者确实有那个选择器。

【问题讨论】:

    标签: objective-c methods intercept


    【解决方案1】:

    您可以通过调配方法调用来做到这一点。假设你想获取 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 中获取更多详细信息。

    【讨论】:

    • 你可能指的是@selector(release),而不是@selector(dealloc)
    • 是的,我的意思是@selector(release)。这个我很可爱,并从一些混合了dealloc的代码中粘贴了这个,但不想在示例中展示它,因为这样做有一些特殊的问题。固定。
    • @LouisGerbarg 能否请您发布一个链接到您的原始来源的链接?
    • 嗯,已经快 11 年了,但这里有一个链接来替换上面损坏的文档指针:developer.apple.com/documentation/objectivec/…
    【解决方案2】:

    拦截Objective-C中的方法调用(假设它是Objective-C,而不是C调用)是通过一种称为method swizzling的技术完成的。

    您可以找到有关如何实现 here 的介绍。有关如何在实际项目中实现方法调配的示例,请查看 OCMock(Objective-C 的隔离框架)。

    【讨论】:

      【解决方案3】:

      在 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 产生副作用。但尝试一下可能会很有趣。

      【讨论】:

        【解决方案4】:

        如果您想记录从应用程序代码发送的消息,-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;
        

        您将有一个消息发送日志。请注意,从被拦截对象内部发送的发送不会被拦截,因为它们将使用原始对象的“自我”指针发送。

        【讨论】:

          【解决方案5】:

          如果您只想记录从外部调用的消息(通常从委托中调用;查看哪种消息、何时等),您可以像这样覆盖 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;
          

          }

          【讨论】:

            【解决方案6】:

            创建NSProxy 的子类并实现-forwardInvocation: 和-methodSignatureForSelector:(或-forwardingTargetForSelector:,如果您只是将其指向第二个对象而不是自己摆弄方法)。

            NSProxy 是一个为实现-forwardInvocation: 而设计的类。它有一些方法,但大多数情况下你不希望它们被抓住。例如,捕获引用计数方法将阻止代理被释放,除非在垃圾收集下。但如果NSProxy 上有特定方法绝对需要转发,则可以专门覆盖该方法并手动调用-forwardInvocation:。请注意,NSProxy 文档下列出的方法并不意味着 NSProxy 实现了它,而只是希望所有代理对象都有它。

            如果这对您不起作用,请提供有关您的情况的更多详细信息。

            【讨论】:

              【解决方案7】:

              也许你想要NSObject 的-forwardInvocation 方法。这使您可以捕获一条消息,重新定位它,然后重新发送它。

              【讨论】:

              • 文档指出,-forwardInvocation 仅在向对象发送没有相应方法的消息时调用。我想要一个在所有情况下都可以调用的方法,即使接收者确实有那个选择器。
              • 这条评论比你原来的问题有更多的信息.​​..... ;)
              • @MarkPowell 我不同意。他最初的问题——“我可以在 Objective-C 中拦截一个方法调用吗?”——已经足够完整了。您的回答虽然有价值,但仅提供了一种拦截一小部分方法调用的方法。
              【解决方案8】:

              您可以使用您自己的方法调用来调整方法调用,它会在“拦截”时执行您想要执行的任何操作并调用原始实现。 Swizzling 是通过 class_replaceMethod() 完成的。

              【讨论】:

                【解决方案9】:

                一个方法调用,不。发送一条消息,是的,但如果你想要一个关于如何发送的好答案,你将不得不更具描述性。

                【讨论】:

                • Obj-C 中方法调用的术语是“消息发送”,因此它们是一个并且它们相同,而不是函数调用 - 你是正确的 - 不能被动态拦截。
                【解决方案10】:

                要在调用方法时执行某些操作,您可以尝试基于事件的方法。因此,当调用该方法时,它会广播一个事件,该事件被任何侦听器接收。我对目标 C 不是很好,但我只是在 Cocoa 中使用 NSNotificationCenter 找到了类似的东西。

                但是如果“拦截”是指“停止”,那么您可能需要更多逻辑来决定是否应该调用该方法。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-10-11
                  • 2015-04-29
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多