iOS: NSProxy can't hook method called inside of Class itself

611 Views Asked by At

I use NSProxy to mock a class, and want to hook all invocation of the class. But only methods called outside the class are hooked, without methods called inside the class. Below is something like my code:

In my AppDelegate.m, TBClassMock is subclass of NSProxy

TBClassMock *mock = [[TBClassMock alloc] init];
TBTestClass *foo = [[TBTestClass alloc] init];
mock.target = foo;

foo = mock;
[foo outsideCalled];

In my TBTestClass.m

- (void)outsideCalled
{
    [self insideCalled];
}

- (void)insideCalled
{

}

In my TBClassMock.m

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    NSLog(@"Signature: %@", NSStringFromSelector(selector));
    return [self.target methodSignatureForSelector:selector];
}

-(void)forwardInvocation:(NSInvocation*)anInvocation
{
    //... Do other things

    [anInvocation invokeWithTarget:self.target];
}

Then I can log the invocation of [foo outsideCalled], but can't log the invocation of [self insideCalled].

I aim to do something in all invocations of the class in //... Do other things, and this way seems failed. Any explanations about this and any other method to implement my requirement? I just don't want to swizzle all methods of the class using method_exchangeImplementations as I think it's too fussy and not a good way.

1

There are 1 best solutions below

0
On

I guess you've misunderstood the concept of NSProxy. The NSProxy stands for a class, it isn't anything in itself. The way a NSProxy is supposed to be used is, you just call all the methods on proxy as if it were the class it is standing for.

In your code, instead of:

foo = mock;
[foo outsideCalled];

Do:

[mock outsideCalled];

This is my entire code:

#import <Foundation/Foundation.h>

@interface TBTestClass : NSObject

- (void)outsideCalled;
- (void)insideCalled;

@end

@implementation TBTestClass
- (void)outsideCalled;
{
    NSLog(@"TBTestClass:outsideCalled");
    [self insideCalled];
}
- (void)insideCalled;
{
    NSLog(@"TBTestClass:insideCalled");
}
@end


@interface TBClassMock : NSProxy
@property (retain) id target;
@end

@implementation TBClassMock

@synthesize target;

- (void)dealloc
{
    self.target = nil;
}

- (id)init
{
    self.target = nil;
    return self;
}

-(void)forwardInvocation:(NSInvocation *)anInvocation
{
    if([self.target respondsToSelector:[anInvocation selector]]){
        [anInvocation invokeWithTarget:self.target];
    }
}

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    return [self.target methodSignatureForSelector:selector];
}

@end

int main(int argc, const char **argv)
{
    @autoreleasepool {
        TBClassMock *mock = [[TBClassMock alloc] init];
        TBTestClass *foo = [[TBTestClass alloc] init];

        mock.target = foo;
        [mock outsideCalled];

        [mock release];
        [foo release];
    }
    return 0;
}

Output:

2014-10-23 05:35:06.043 a.out[25483:507] TBTestClass:outsideCalled
2014-10-23 05:35:06.047 a.out[25483:507] TBTestClass:insideCalled