views:

821

answers:

9

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.

A: 

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.

gargantaun
I think he's referring to metaprogramming techniques.
Geo
+1  A: 

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

MarkPowell
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.
luvieere
This comment has significantly more information than your original question... ;)
MarkPowell
I'll edit, thanks for pointing out.
luvieere
+1  A: 

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.

Azeem.Butt
+1  A: 

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().

Graham Lee
A: 

I'd like a method to be called under all circumstances, even if the receiver does have that selector.

NSObject has a nice method called -[NSObject respondsToSelector:]. It sends you a SEL and you return a BOOL. You're welcome to override it and return whatever you want. If you decide to go with the default value, just return [MySuperClass instancesRespondToSelector:aSelector];

Dave DeLong
Does *respondsToSelector* get called before every selector invocation, even if itself is not explicitly called?
luvieere
I believe so. In addition, my recommendation on `[super respondsToSelector:aSelector]` was incorrect. The linked documentation shows how to implement it properly.
Dave DeLong
You should edit and correct that bit of detail, your answer is a good candidate to being accepted, and it would be a shame to mislead others who may read it and try to implement it like it is.
luvieere
Should I override *respondsToSelector* in my class, or in a category of NSObject? I've tried overriding it in my class, an UIViewController subclass, btw, and it doesn't get called when I call a method from within it. Am I missing something here?
luvieere
I think Dave is wrong, and `-respondsToSelector:` isn't called automatically. You can use it for checking whether a method exists before sending a message that would cause an exception. Delegating classes frequently use it, but just sending a message does not. Imagine the overhead that would add to every single message send!
Quinn Taylor
+1 for Quinn, this answer is incorrect. Overriding the various -respondsToSelector: methods will not be a way to intercept messages, except those which are answered by running -respondsToSelector:.
Graham Lee
+10  A: 

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).

Johannes Rudolph
+3  A: 

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.

Ole Begemann
+6  A: 

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.

Louis Gerbarg
You probably meant @selector(release), not @selector(dealloc)
BJ Homer
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.
Louis Gerbarg
A: 

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.

John Calsbeek