Skip to content

Instantly share code, notes, and snippets.

@NSExceptional
Last active November 20, 2016 07:28
Show Gist options
  • Save NSExceptional/4c8f49210a45c9782c2717109e3d2904 to your computer and use it in GitHub Desktop.
Save NSExceptional/4c8f49210a45c9782c2717109e3d2904 to your computer and use it in GitHub Desktop.
Use the Objc runtime and va_args to determine and use method parameters at runtime.
#import "TypeEncodings.h"
@interface Foo : NSObject
- (char)bar:(NSString *)bar baz:(int)b;
@end
@implementation Foo
- (char)bar:(NSString *)bar baz:(int)b {
printf("%s\n", bar.UTF8String);
return '#';
}
@end
int main() {
// NSInvocation hack to invoke the desired IMP
void (*invokeWithIMP)(id, SEL, IMP) = (void *)[NSInvocation instanceMethodForSelector:NSSelectorFromString(@"invokeUsingIMP:")];
@autoreleasepool {
Method method = Method(@selector(bar:baz:), [Foo class]);
IMP orig = originalIMP(method);
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:NSMethodSignatureOfMethod(method)];
TypeEncoding returnType = invocation.methodSignature.methodReturnType[0];
IMP imp = imp_implementationWithBlock(^void *(id receiver, ...) {
// Give invocation the original arguments
va_list args;
va_start(args, receiver);
for (NSInteger i = 2; i < invocation.methodSignature.numberOfArguments; i++) {
const char *typec = [invocation.methodSignature getArgumentTypeAtIndex:i];
TypeEncoding type = typec[0];
switch (type) {
case TypeEncodingInt: {
int baz = va_arg(args, int);
[invocation setArgument:&baz atIndex:i];
printf("arg2: %d\n", baz);
break;
}
case TypeEncodingObjcObject: {
id bar = va_arg(args, id);
[invocation setArgument:&bar atIndex:i];
printf("%s", [NSString stringWithFormat:@"arg1: %@\n", bar].UTF8String);
break;
}
default: {
printf("Some other type we don't care about: %c\n", (char)type);
exit(1);
}
}
}
// Invoke method with original IMP
invocation.target = receiver;
invokeWithIMP(invocation, 0, orig);
// We know the return length is greater than zero for this
// case, but if you wanted to dynamically return a value
// or not based on the return type, this is how to do it
if (invocation.methodSignature.methodReturnLength) {
void *ret = NULL;
[invocation getReturnValue:&ret];
return ret;
} else {
return NULL;
}
});
// Make Foo use our new method implementation
swizzle(method, imp);
Foo *f = [Foo new];
char ret = [f bar:@"hello" baz:5];
printf("Returned: %c\n", ret);
}
}
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#define Method(sel, cls) class_getInstanceMethod(cls, sel)
#define originalIMP(m) method_getImplementation(m)
#define NSMethodSignatureOfMethod(m) [NSMethodSignature signatureWithObjCTypes:method_getTypeEncoding(m)]
#define swizzle(m, imp) method_setImplementation(m, imp)
typedef NS_ENUM(NSUInteger, TypeEncoding)
{
TypeEncodingUnknown = '?',
TypeEncodingChar = 'c',
TypeEncodingInt = 'i',
TypeEncodingShort = 's',
TypeEncodingLong = 'l',
TypeEncodingLongLong = 'q',
TypeEncodingUnsignedChar = 'C',
TypeEncodingUnsignedInt = 'I',
TypeEncodingUnsignedShort = 'S',
TypeEncodingUnsignedLong = 'L',
TypeEncodingUnsignedLongLong = 'Q',
TypeEncodingFloat = 'f',
TypeEncodingDouble = 'd',
TypeEncodingCBool = 'B',
TypeEncodingVoid = 'v',
TypeEncodingCString = '*',
TypeEncodingObjcObject = '@',
TypeEncodingObjcClass = '#',
TypeEncodingSelector = ':',
TypeEncodingArray = '[',
TypeEncodingStruct = '{',
TypeEncodingUnion = '(',
TypeEncodingBitField = 'b',
TypeEncodingPointer = '^'
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment