Intercept method call in Objective-C
Asked Answered
Y

10

19

Can I intercept a method call in Objective-C? How?

Edit: Mark Powell's answer gave me a partial solution, the -forwardInvocation method. But the documentation states that -forwardInvocation is only called when an object is sent a message for which it has no corresponding method. I'd like a method to be called under all circumstances, even if the receiver does have that selector.

Yeseniayeshiva answered 24/10, 2009 at 16:38 Comment(0)
C
24

You do it by swizzling the method call. Assuming you want to grab all releases to 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@:");

You can get more details in the Objective C runtime documentation.

Carrousel answered 29/10, 2009 at 9:35 Comment(4)
You probably meant @selector(release), not @selector(dealloc)Distal
Yeah, I did mean @selector(release). This I cute and paste this from some code that swizzled dealloc, but didn't want to show that in example, because there are some special issues with doing that. Fixed.Carrousel
@LouisGerbarg Can you please post a link to your original source that swizzled release?Indecisive
Well, it's almost 11 years on, but here's a link to replace the broken documentation pointer above: developer.apple.com/documentation/objectivec/…Junitajunius
L
16

Intercepting method calls in Objective-C (asuming it is an Objective-C, not a C call) is done with a technique called method swizzling.

You can find an introduction on how to implement that here. For an example how method swizzling is implemented in a real project check out OCMock (an Isolation Framework for Objective-C).

Luxury answered 24/10, 2009 at 17:15 Comment(0)
E
11

Sending a message in Objective-C is translated into a call of the function objc_msgSend(receiver, selector, arguments) or one of its variants objc_msgSendSuper, objc_msgSend_stret, objc_msgSendSuper_stret.

If it was possible to change the implementation of these functions, we could intercept any message. Unfortunately, objc_msgSend is part of the Objective-C runtime and cannot be overridden.

By googling I found a paper on Google Books: A Reflective Architecture for Process Control Applications by Charlotte Pii Lunau. The paper introduces a hack by redirecting an object's isa class pointer to an instance of a custom MetaObject class. Messages that were intended for the modified object are thus sent to the MetaObject instance. Since the MetaObject class has no methods of its own, it can then respond to the forward invocation by forwarding the message to the modified object.

The paper does not include the interesting bits of the source code and I have no idea if such an approach would have side effects in Cocoa. But it might be interesting to try.

Extent answered 27/10, 2009 at 16:26 Comment(0)
A
5

If you want to log message sends from your application code, the -forwardingTargetForSelector: tip is part of the solution.
Wrap your object:

@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

Now do something like this:

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

and you will have a log of message sends. Note, sends sent from within the intercepted object won't be intercepted, as they will be sent using the original object 'self' pointer.

Aspic answered 20/8, 2011 at 6:53 Comment(0)
C
3

If you only want to log messages called from the outside (usually called from delegates; to see which kind of messages, when, etc.), you can override respondsToSelector like this:

- (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;

}

Colporteur answered 19/1, 2012 at 11:56 Comment(0)
C
2

Create a subclass of NSProxy and implement -forwardInvocation: and -methodSignatureForSelector: (or -forwardingTargetForSelector:, if you're simply directing it on to a second object instead of fiddling with the method yourself).

NSProxy is a class designed for implementing -forwardInvocation: on. It has a few methods, but mostly you don't want them to be caught. For example, catching the reference counting methods would prevent the proxy from being deallocated except under garbage collection. But if there are specific methods on NSProxy that you absolutely need to forward, you can override that method specifically and call -forwardInvocation: manually. Do note that simply because a method is listed under the NSProxy documentation does not mean that NSProxy implements it, merely that it is expected that all proxied objects have it.

If this won't work for you, provide additional details about your situation.

Contractual answered 30/10, 2009 at 15:50 Comment(0)
T
1

Perhaps you want NSObject's -forwardInvocation method. This allows you to capture a message, retarget it and then resend it.

Tetroxide answered 24/10, 2009 at 16:54 Comment(3)
The documentation states that -forwardInvocation is only called when an object is sent a message for which it has no corresponding method. I'd like a method to be called under all circumstances, even if the receiver does have that selector.Yeseniayeshiva
This comment has significantly more information than your original question... ;)Tetroxide
@Tetroxide I disagree. His original question—"Can I intercept a method call in Objective-C?"—was complete enough. Your answer, while worthwhile, only provides a way to intercept a small percentage of method calls.Brathwaite
B
1

You can swizzle the method call with one of your own, which does whatever you want to do on "interception" and calls through to the original implementation. Swizzling is done with class_replaceMethod().

Bibber answered 24/10, 2009 at 17:6 Comment(0)
R
0

A method call, no. A message send, yes, but you're going to have to be a lot more descriptive if you want a good answer as to how.

Recap answered 24/10, 2009 at 16:57 Comment(1)
The term for a method call in Obj-C is "a message send" so they are one and they same, as opposed to a function call which - you are correct - cannot be dynamically intercepted.Kirk
E
-1

To do something when a method is called, you could try an events based approach. So when the method is called, it broadcasts an event, which is picked up by any listeners. I'm not great with objective C, but I just figured out something similar using NSNotificationCenter in Cocoa.

But if by "intercept" you mean "stop", then maybe you need more logic to decide wether the method should be called at all.

Elba answered 24/10, 2009 at 16:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.