Swizzle an unknown selector of a specific class at runtime - objective-c

I have a class with the following method in one of my library's public headers. A user of my class will pass in the selector and class.
-(void)someMethodWithSelector(SEL)aSelector ofClass:(Class)clazz
At compile time, I don't know what the selector will look like, how many parameters will be passed, etc... but what I want is to be able to swizzle the passed selector at runtime, perform some extra logic, and call the original method afterwards.
I know how to swizzle class and instance methods, but I'm unsure how I would proceed given this scenario.
Has anyone had any experience dealing with a similar approach?

MikeAsh
was able to figure out this problem, so all the credit of this answer goes to him
#import Foundation;
#import ObjectiveC;
static NSMutableSet *swizzledClasses;
static NSMutableDictionary *swizzledBlocks; // Class -> SEL (as string) -> block
static IMP forwardingIMP;
static dispatch_once_t once;
void Swizzle(Class c, SEL sel, void (^block)(NSInvocation *)) {
dispatch_once(&once, ^{
swizzledClasses = [NSMutableSet set];
swizzledBlocks = [NSMutableDictionary dictionary];
forwardingIMP = class_getMethodImplementation([NSObject class], #selector(thisReallyShouldNotExistItWouldBeExtremelyWeirdIfItDid));
});
if(![swizzledClasses containsObject: c]) {
SEL fwdSel = #selector(forwardInvocation:);
Method m = class_getInstanceMethod(c, fwdSel);
__block IMP orig;
IMP imp = imp_implementationWithBlock(^(id self, NSInvocation *invocation) {
NSString *selStr = NSStringFromSelector([invocation selector]);
void (^block)(NSInvocation *) = swizzledBlocks[c][selStr];
if(block != nil) {
NSString *originalStr = [#"omniswizzle_" stringByAppendingString: selStr];
[invocation setSelector: NSSelectorFromString(originalStr)];
block(invocation);
} else {
((void (*)(id, SEL, NSInvocation *))orig)(self, fwdSel, invocation);
}
});
orig = method_setImplementation(m, imp);
[swizzledClasses addObject: c];
}
NSMutableDictionary *classDict = swizzledBlocks[c];
if(classDict == nil) {
classDict = [NSMutableDictionary dictionary];
swizzledBlocks[(id)c] = classDict;
}
classDict[NSStringFromSelector(sel)] = block;
Method m = class_getInstanceMethod(c, sel);
NSString *newSelStr = [#"omniswizzle_" stringByAppendingString: NSStringFromSelector(sel)];
SEL newSel = NSSelectorFromString(newSelStr);
class_addMethod(c, newSel, method_getImplementation(m), method_getTypeEncoding(m));
method_setImplementation(m, forwardingIMP);
}
Here is how we would call the function:
int main(int argc, const char * argv[]) {
#autoreleasepool {
Swizzle([NSBundle class], #selector(objectForInfoDictionaryKey:), ^(NSInvocation *inv) {
NSLog(#"invocation is %# - calling now", inv);
[inv invoke];
NSLog(#"after");
});
NSLog(#"%#", [[NSBundle bundleForClass: [NSString class]] objectForInfoDictionaryKey: (__bridge NSString *)kCFBundleVersionKey]);
}
return 0;
}

Related

objective c gets EXC_BAD_ACCESS error on completionhandler

i'm new to objective-c, please bear with me if i ask stupid questions :)
The following is part of code i have to start vpn tunnel, but keeps getting EXC_BAD_ACCESS error
- (void)startTunnelWithOptions:(NSDictionary *)options completionHandler:(void (^)(BOOL * error))completionHandler {
vpnAdapter = [[OpenAdapter alloc] init];
vpnAdapter.delegate = self;
// get config
config = [[NSDictionary alloc] init];
NETunnelProviderProtocol *protocol = (NETunnelProviderProtocol *)self.protocolConfiguration;
config = protocol.providerConfiguration;
host = config[#"server"];
// Load config data
username = config[#"username"];
password = config[#"password"];
if(option != nil){
[vpnAdapter connect:host user:username pass:password add:YES completionHandler:^(BOOL success){
// return success;
completionHandler(&success); // Thread 2: EXC_BAD_ACCESS (code=1, address=0xbcc68f020)
}];
}else{
[vpnAdapter connect:host user:username pass:password add:NO completionHandler:^(BOOL success){
completionHandler(&success);
}];
}
}
here is connect method
- (void)connect: (NSString *) host user:(NSString *)username pass:(NSString *) password add:(Boolean) isAdd completionHandler:(void (^)(BOOL success)) completionHandler{
dispatch_queue_attr_t attributes = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
dispatch_queue_t connectQueue = dispatch_queue_create("me.ss-abramchuk.open-adapter.connection", attributes);
dispatch_async(connectQueue, ^{
// Call connect
//int ret=1;
NSArray* options = [NSArray arrayWithObjects:
#"--user", username,
host,
nil];
if(isAdd){
options = [NSArray arrayWithObjects:
#"--user", username,
#"--protocol", #"ad",
host,
nil];
}
//NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
//NSString *documentsDirectory = [paths objectAtIndex:0];
NSMutableArray *arguments = [NSMutableArray arrayWithCapacity:1+[options count]];
[arguments addObject:#"connect"];
[arguments addObjectsFromArray:options];
int argc = [arguments count];
char **argv = (char **)malloc(sizeof(char*) * (argc + 1));
[arguments enumerateObjectsUsingBlock:^(NSString *option, NSUInteger i, BOOL *stop) {
const char * c_string = [option UTF8String];
int length = (int)strlen(c_string);
char *c_string_copy = (char *) malloc(sizeof(char) * (length + 1));
strcpy(c_string_copy, c_string);
argv[i] = c_string_copy;
}];
argv[argc] = NULL;
const char *cfPass=[password UTF8String];
int ret = self.vpnClient->start2connect(argc, argv, cfPass);
BOOL result;
if (ret!=0){
result=false;
}
else {result = true;}
completionHandler(result);
});
}
all of these are from networkextension and while debugging, i found int ret = self.vpnClient->start2connect(argc, argv, cfPass);
seems not returning any value.
however, i confirmed that the start2connect method does return int value
so for now, anyone can help explain what's wrong?
thanks
The BOOL * is a pointer to a BOOL. We don’t use that pattern very often. We use it where the block needs to update a BOOL property somewhere, e.g. in enumerateMatchesinString, where you can update the boolean that stop points to in order to stop the enumeration.
But this is a completion handler, so there’s no point in passing a pointer to the boolean (one that presumably was on the stack, inviting problems). Just pass the boolean itself, not a pointer to it.
I would suggest that instead of:
- (void)startTunnelWithOptions:(NSDictionary *)options completionHandler:(void (^)(BOOL * error))completionHandler {
...
[vpnAdapter connect:host user:username pass:password add:YES completionHandler:^(BOOL success){
completionHandler(&success);
}];
...
}
That you want:
- (void)startTunnelWithOptions:(NSDictionary *)options completionHandler:(void (^)(BOOL success))completionHandler {
...
[vpnAdapter connect:host user:username pass:password add:YES completionHandler:^(BOOL success){
completionHandler(success);
}];
...
}
Note the block parameter isn’t BOOL * error but rather BOOL success and when it calls the completionHandler, there’s not & before success.
If there’s some reason you needed to update the BOOL, then that’s a different matter, but it doesn’t make sense in the context of a completion handler.

Video playback Mirroring prevention workaround

Environment
Xcode # OS-X Yosemite
iOS App # Obj-C
Use-case
Quicktime mirroring session is set between the iOS & the OSX (How do I set up a mirroring session between iOS 8 and Yosemite? )
A 3rd party SDK is integrated with the iOS app
The SDK is used for Video playback
When video is playing and the mirroring session is set ( as External Display ) no video is playing ( only audio )
The SDK doesn’t have any API to control external video playback/mirroring ON/OFF while mirroring
I need to be able to mirror the video to my OSX Desktop, for that, I have tried hiding the mirrored screen from the 3rd party SDK in the following manner ( this however didn’t help ):
namespace NSNotificationCenterS
{
namespace Original
{
IMP addObserver = 0;
}
void addObserver(id self, SEL _cmd, id notificationObserver, SEL notificationSelector, NSString* notificationName, id notificationSender){
if( (YES == [notificationName isEqualToString:UIScreenDidConnectNotification]) ||
(YES == [notificationName isEqualToString:UIScreenDidDisconnectNotification]) ||
(YES == [notificationName isEqualToString:UIScreenModeDidChangeNotification ]) )
{
NSLog(#"NSNotificationCenter addObserver(%#, %#, '%#', %#) SUPRESSED!!!", notificationObserver, NSStringFromSelector(notificationSelector), notificationName, notificationSender);
return;// Supress notifications of this kind of events
}
// NSLog(#"NSNotificationCenter addObserver(%#, %#, '%#', %#)", notificationObserver, NSStringFromSelector(notificationSelector), notificationName, notificationSender);
((void(*)(id, SEL, id, SEL, NSString*,id))Original::addObserver)(self, _cmd, notificationObserver, notificationSelector, notificationName, notificationSender);
}
void initHooks() {
Method method;
method = class_getInstanceMethod([NSNotificationCenter class], #selector(addObserver:selector:name:object:));
Original::addObserver = method_getImplementation(method);
method_setImplementation(method, (IMP)NSNotificationCenterS::addObserver);
}
}
namespace AVPlayerS
{
namespace Original
{
IMP init = 0;
IMP setAllowsExternalPlayback = 0;
IMP setUsesExternalPlaybackWhileExternalScreenIsActive = 0;
IMP setAllowsAirPlayVideo = 0;
IMP setUsesAirPlayVideoWhileAirPlayScreenIsActive = 0;
}
id init(id self, SEL _cmd) {
NSLog(#"AVPlayer init, %#\n", self);
id ret = ((id(*)(id,SEL))Original::init)(self, _cmd);
if(nil == ret)
return nil;
((void(*)(id, SEL, BOOL))Original::setAllowsExternalPlayback)(ret, #selector(setAllowsExternalPlayback:), YES);
((void(*)(id, SEL, BOOL))Original::setUsesExternalPlaybackWhileExternalScreenIsActive)(ret, #selector(setUsesExternalPlaybackWhileExternalScreenIsActive:), YES);
((void(*)(id, SEL, BOOL))Original::setAllowsAirPlayVideo)(ret, #selector(setAllowsAirPlayVideo:), YES);
((void(*)(id, SEL, BOOL))Original::setUsesAirPlayVideoWhileAirPlayScreenIsActive)(ret, #selector(setUsesAirPlayVideoWhileAirPlayScreenIsActive:), YES);
NSLog(#"AVPlayer, %d, %d, %d, %d\n", [ret allowsExternalPlayback], [ret usesExternalPlaybackWhileExternalScreenIsActive], [ret allowsAirPlayVideo], [ret usesAirPlayVideoWhileAirPlayScreenIsActive]);
return ret;
}
UIScreen* mirroredScreen(id self, SEL _cmd) {
return nil;
}
NSArray* getScreens(id self, SEL _cmd) {
return [NSArray arrayWithObject:[UIScreen mainScreen]];
}
void flag_stub(id self, SEL _cmd, BOOL bSet){
NSLog(#"AVPlayer flag_stub(%#, %#, '%s')", self, NSStringFromSelector(_cmd), bSet ? "true" : "false");
}
BOOL ret_YES(id self, SEL _cmd) {
NSLog(#"AVPlayer ret_YES(%#, %#)", self, NSStringFromSelector(_cmd));
return YES;
}
BOOL ret_NO(id self, SEL _cmd) {
NSLog(#"AVPlayer ret_NO(%#, %#)", self, NSStringFromSelector(_cmd));
return NO;
}
void initHooks() {
Method method;
method = class_getInstanceMethod([UIScreen class], #selector(mirroredScreen));
method_setImplementation(method, (IMP)AVPlayerS::mirroredScreen);
method = class_getClassMethod([UIScreen class], #selector(screens));
method_setImplementation(method, (IMP)AVPlayerS::getScreens);
method = class_getInstanceMethod([AVPlayer class], #selector(init));
AVPlayerS::Original::init = method_getImplementation(method);
method_setImplementation(method, (IMP)AVPlayerS::init);
method = class_getInstanceMethod([AVPlayer class], #selector(setAllowsExternalPlayback:));
AVPlayerS::Original::setAllowsExternalPlayback = method_getImplementation(method);
method_setImplementation(method, (IMP)flag_stub);
method = class_getInstanceMethod([AVPlayer class], #selector(setUsesExternalPlaybackWhileExternalScreenIsActive:));
AVPlayerS::Original::setUsesExternalPlaybackWhileExternalScreenIsActive = method_getImplementation(method);
method_setImplementation(method, (IMP)flag_stub);
method = class_getInstanceMethod([AVPlayer class], #selector(setAllowsAirPlayVideo:));
AVPlayerS::Original::setAllowsAirPlayVideo = method_getImplementation(method);
method_setImplementation(method, (IMP)flag_stub);
method = class_getInstanceMethod([AVPlayer class], #selector(setUsesAirPlayVideoWhileAirPlayScreenIsActive:));
AVPlayerS::Original::setUsesAirPlayVideoWhileAirPlayScreenIsActive = method_getImplementation(method);
method_setImplementation(method, (IMP)flag_stub);
method = class_getInstanceMethod([AVPlayer class], #selector(isExternalPlaybackActive));
method_setImplementation(method, (IMP)ret_NO);
method = class_getInstanceMethod([AVPlayer class], #selector(isAirPlayVideoActive));
method_setImplementation(method, (IMP)ret_NO);
method = class_getInstanceMethod([AVPlayer class], #selector(allowsAirPlayVideo));
//method_setImplementation(method, (IMP)ret_YES);
method_setImplementation(method, (IMP)ret_NO);
}
}
How can I go around the Video playback mirroring protection? is there any lower level API I should intercept ?

Objective-c recursive blocks with threads EXC_BAD_ACCESS

I have some recursive block code in objective-c that is causing a EXC_BAD_ACCESS error.
- (void) doSomethingWithCompletion:(void (^)())completion {
if (completion) {
dispatch_async(dispatch_get_main_queue(), completion);
}
}
- (void) testBlocks {
NSString *testString = #"hello";
__block NSInteger count = 0;
__block __weak void (^weak_block)(NSString *);
void(^strong_block)(NSString *);
weak_block = strong_block = ^(NSString *str) {
[self doSomethingWithCompletion:^{
NSLog(#"number: %zd", count);
if (++count < 10) {
weak_block(str);
}
}];
};
strong_block(testString);
}
The error happens on weak_block(str) which i assume is because it is released when dispatch_async is called. Calling strong_block(str) in it's place when it's declared with __block like so:
__block void(^strong_block)(NSString *);
Causes a warning 'Capturing 'strong_block' strongly in this block is likely to lead to a retain cycle'.
So I changed the testBlock method to not use a weak reference like so:
- (void) testBlocks {
NSString *testString = #"hello";
__block NSInteger count = 0;
__block void (^inner_block)(NSString *);
void(^strong_block)(NSString *);
inner_block = strong_block = ^(NSString *str) {
[self doSomethingWithCompletion:^{
NSLog(#"number: %zd", count);
if (++count < 10) {
inner_block(str);
}
}];
};
strong_block(testString);
}
But I am unsure if this causes a retain cycle or if adding
__block void (^inner_block)(NSString *) = weak_block;
inside the block instead would cause a retain cycle as well. What is the correct way to handle this situation?
It crashes because the block (pointed to by weak_block, strong_block) has already been deallocated by the time the "completion" block runs, and calling a block with a nil block pointer crashes.
The block is deallocated because there are no strong references to it after testBlocks returns.
The second one will have a retain cycle because the block captures inner_block, which holds a strong reference to itself.
The proper way is to make a strong reference from the captured weak reference inside the block, and let the completion block capture that:
- (void) testBlocks {
NSString *testString = #"hello";
__block NSInteger count = 0;
__block __weak void (^weak_block)(NSString *);
void(^strong_block)(NSString *);
weak_block = strong_block = ^(NSString *str) {
void(^inner_block)(NSString *) = weak_block;
[self doSomethingWithCompletion:^{
NSLog(#"number: %zd", count);
if (++count < 10) {
inner_block(str);
}
}];
};
strong_block(testString);
}
Not sure this is proof of either possibility but if you add a weak block property and examine that one after all block stuff has run...
...
#property (weak) void (^true_weak_block)(NSString *);
#property (weak) NSString *weak_string;
...
- (void) testBlocks {
NSString *strong_string = [NSString stringWithFormat:#"%#", #"some string"]; // note that you can not use a string literal in this example..
self.weak_string = strong_string;
NSString *testString = #"hello";
__block NSInteger count = 0;
__block void (^inner_block)(NSString *);
void(^strong_block)(NSString *);
inner_block = strong_block = ^(NSString *str) {
[self doSomethingWithCompletion:^{
NSLog(#"number: %zd", count);
if (++count < 10) {
inner_block(str);
}
}];
};
self.true_week_block = strong_block;
[self test];
strong_block(testString);
}
- (void)test {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"%#", self.true_week_block); // not deallocated
NSLog(#"%#", self.weak_string); // deallocated
});
}
In my test the block is never deallocated and also the memory address remains the same over time, even if you change regular assignment of the two strong blocks to use copy instead of the implicitly retained assignment.

Unit testing a method that relies on an NSMapTable to clean up objects that lack strong references

So I have the following method (it's an UIView category method to supplement nib loading, however, it has been cleaned up to be more relevant here):
+ (id) loadFromNib {
NSString* nibName = NSStringFromClass([self class]);
NSArray* elements = [[NSBundle mainBundle] loadNibNamed:nibName owner:nil options:nil];
NSMutableArray* foundCustomObjects = [NSMutableArray array];
NSObject* foundViewObject = nil;
for (NSObject* anObject in elements) {
if ([anObject isKindOfClass:[self class]] && foundViewObject == nil) {
foundViewObject = anObject;
// Keep strong references to non-UIView custom objects (to prevent them from being released due to having weak-only references):
} else if (![anObject isKindOfClass:[UIView class]]) {
[foundCustomObjects addObject:anObject];
}
}
// Generate strong references to all found custom objects:
if (foundViewObject != nil) {
[foundCustomObjects enumerateObjectsUsingBlock:
^(id obj, NSUInteger idx, BOOL *stop) {
[customObjects setObject:foundViewObject forKey:obj];
// (Yes, I will skip objects that are strongly referenced by their view later on)
}];
}
return foundViewObject;
}
And customObjects is a static variable defined as:
+ (void) initialize {
if (customObjects == nil) {
// For each view that holds a custom object, store a strong reference to that object here, that way preventing the object from being deallocated due to weak referencing (in UICollectionView.delegate, for example):
customObjects = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory];
}
}
My problem is that I want to unit test the fact that deallocated views really result in deallocating the referenced "custom object". How should I do that?
This is what I've got so far (using OCMock):
- (void) test {
/*
* SETUP */
NSObject* __weak weakRefToSomeObject;
UIView* someView;
NSObject* someObject;
#autoreleasepool {
someView = [[UIView alloc] init];
someObject = [[NSObject alloc] init];
NSArray* nibElements = #[someView, someObject];
id mainBundleMock = [OCMockObject niceMockForClass:[NSBundle class]];
[[[mainBundleMock stub] andReturn:nibElements] loadNibNamed:[OCMArg any] owner:[OCMArg any] options:[OCMArg any]];
id NSBundleMock = [OCMockObject niceMockForClass:[NSBundle class]];
[[[NSBundleMock stub] andReturn:mainBundleMock] mainBundle];
/*
* RUN */
weakRefToSomeObject = someObject;
[UIView loadFromNib];
someObject = nil;
nibElements = nil;
[mainBundleMock stopMocking];
[NSBundleMock stopMocking];
mainBundleMock = nil;
NSBundleMock = nil;
}
/*
* VERIFY */
XCTAssertNotNil(weakRefToSomeObject); // This passes!
#autoreleasepool {
someView = nil;
}
XCTAssertNil(weakRefToSomeObject); // This does not pass - why?
}
At the last row, I expect the key-value pair (where the view was referenced weakly) to be dropped, that way dropping the last strong reference to someObject, and thus rendering weakRefToSomeObject nil.
I have also tried to add someView = nil to the first autoreleasepool (just below NSBundleMock = nil), but that didn't help.
Any ideas?
I fixed this by adding an access function:
// Allow tests to access the customObjects map:
NSMapTable* getCustomObjectsMap() {
return customObjects;
}
And then declaring it in my unit test document:
// Declare method that gives us access to the static customObjects variable:
NSMapTable* getCustomObjectsMap();
Thus the test code ended up to be:
- (void) testCustomObjectLifecycleFromStartToFinish {
/*
* ASSERT REQUIRED INITIAL STATE */
XCTAssertNil([[getCustomObjectsMap() objectEnumerator] nextObject]);
/*
* SETUP */
UIView* someView = [[UIView alloc] init];
NSObject* someObject = [[NSObject alloc] init];
#autoreleasepool {
#autoreleasepool {
NSArray* nibElements = #[someView, someObject];
id mainBundleMock = [OCMockObject niceMockForClass:[NSBundle class]];
[[[mainBundleMock stub] andReturn:nibElements] loadNibNamed:[OCMArg any] owner:[OCMArg any] options:[OCMArg any]];
id NSBundleMock = [OCMockObject niceMockForClass:[NSBundle class]];
[[[NSBundleMock stub] andReturn:mainBundleMock] mainBundle];
/*
* RUN */
[UIView loadFromNib];
someObject = nil;
nibElements = nil;
}
/*
* VERIFY */
XCTAssertNotNil([[getCustomObjectsMap() objectEnumerator] nextObject]);
// Dropping last strong reference to view:
someView = nil;
}
// Without strong references to someView, the objects map should have been emptied:
XCTAssertNil([[getCustomObjectsMap() objectEnumerator] nextObject]);
}

Objective C - Get argument types of a method?

At runtime I need to be able to get the argument types of a method. The following is what gets printed:
I have read on other threads that at run-time time objective c treats all objects passed to a method as arguments as id. If this approach doesn't work any other suggestions on a way to read argument types?
Log
2014-02-07 15:47:08.962 OCInjection[55727:70b] #
2014-02-07 15:47:08.964 OCInjection[55727:70b] :
Code
Class class = NSClassFromString(injectionBinding);
unsigned int methodCount;
Method *methodList = class_copyMethodList(class, &methodCount);
for (int i = 0; i < methodCount; i++)
{
Method method = methodList[i];
SEL selector = method_getName(method);
NSMethodSignature *signature = [class instanceMethodSignatureForSelector:selector];
NSUInteger numberOfArguments = [signature numberOfArguments];
for (int i=0 ; i<numberOfArguments ; i++)
{
NSString *type = [NSString stringWithUTF8String:[signature getArgumentTypeAtIndex:i]];
NSLog(type);
}
}
According to
-getArgumentTypeAtIndex:
and
Decode Class from #encoded type string
I think there is no method to get the "real" argument type.
Doesn't seem like it's possible to do this. I ended up using a proxy object to send the message to, and capture it. Probably not the ideal way, but I haven't found a better solution.
#interface DIContructorInjectorProxy()
#property (nonatomic, strong) id realObject;
#end
#implementation DIContructorInjectorProxy
#define Inject(x) [DIContructorInjectorProxy _injectMacro:x]
- (id)initWithClass:(Class)class
{
self.realObject = [[class alloc] init];
}
+ (id)_injectMacro:(id)x
{
if ([x isKindOfClass:NSClassFromString(#"Protocol")])
return NSStringFromProtocol(x);
else
return NSStringFromClass(x);
}
- (id)withConstructor
{
// Just making the method call for defining a constructor more readable by a call to this method first
return self;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSMutableString *selectorName = [NSStringFromSelector(anInvocation.selector) mutableCopy];
NSUInteger numberOfColonsInMethodName = [selectorName replaceOccurrencesOfString:#":"
withString:#":"
options:NSLiteralSearch
range:NSMakeRange(0, selectorName.length)];
[anInvocation retainArguments];
NSMutableArray *argumentsPassedToSelector = [NSMutableArray array];
for (int i=2 ; i<numberOfColonsInMethodName+2 ; i++)
{
NSString *argument;
[anInvocation getArgument:&argument atIndex:i];
[argumentsPassedToSelector addObject:[NSString stringWithFormat:#"%#", argument]];
}
// Store arguments somewhere
return;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
return [self.realObject methodSignatureForSelector:aSelector];
}
#end
How the user uses this to define method arguments
[self bindProtocol:#protocol(DataStorage) toClass:[InMemoryDataStorage class]];
// withConstructor returns an appropriate proxy object
// Then when the init method is called, it calls forwardInvocation,
// and from there I save all the info I need about the method and arguments
(void)[[[self bindProtocol:#protocol(GoogleClient) toClass:[GoogleClientEngine class]] withConstructor]
initWithDataStorage:Inject(#protocol(DataStorage))];