So, after a lot of work, I realized this myself.
Problem
The reality is that it is impossible to pass varargs to the C vararg function. For this, you really need an API to provide a v style function, such as vprintf . Now actually there is a objc_msgSendv , but it is deprecated. In addition, there is no objc_msgSendSuperv . This means that the low-level API is completely disconnected from us. This API is only useful if you know the exact signature of the message at compile time.
The only other option is to use the NSInvocation mechanism and create a message, rather than directly accessing the implementations. The naive solution is pretty trivial, but there are a lot of caveats to this.
There are a few things we need to solve:
- Passing arguments of different types (read sizes). We will copy our arguments to our
NSInvocation using va_arg , but this macro depends on how we pass the argument type to it to determine the size of the bytes to copy and move. - Calling an overridden implementation (reading hidden) is different. In addition, since we are just sending messages now, there is no way to choose the exact implementation of the class. Essentially, we can only do
objc_msgSend , not objc_msgSendSuper . It is not possible to directly call a method that has been overridden by another. - Return the return value. Our new IMP function should have a return type that matches the type of return function we are replacing. OP used the return value of
void , but this, of course, is not always the case. A further difficulty is that this return value must be specified at compile time, as it is a C function, not a runtime.
Decision
I really solved the above problems in the code available here:
https://github.com/Lyndir/Pearl/blob/master/Pearl/NSInvocation%2BPearl.h
https://github.com/Lyndir/Pearl/blob/master/Pearl/NSInvocation%2BPearl.m
To solve these problems, I did the following:
- For each argument, we consider a method signature that encodes the type of the argument. We determine the byte size of the argument type and make an
if series to check if the argument size is equal to the known size, which allows us to fix a fixed va_arg time va_arg for this size. This solves the problem for type arguments with a size equal to a series of "supported" sizes. In my implementation, I support any byte size 1 (e.g. char), 2 (e.g. short), 4 (e.g. int), 8 (e.g. id), 16 (e.g. CGPoint), 32 (e.g. CGRect), 48 (e.g. CGAffineTransform) (see - (void)setArguments:(va_list)args ). - To invoke an implementation of a method that has been overridden by another method, we can look for a hidden implementation using
class_getInstanceMethod and method_getImplementation , and then we can set this implementation to a new temporary proxy method in the object class using class_addMethod . If you then call the proxy method instead of the target method, the runtime will invoke the hidden implementation we were looking for, effectively doing the same thing as msgSendSuper . However, this will lead to the fact that we will refer to the implementation with bad _cmd , so instead we assign the hidden implementation to the top level with the correct method name. This will overwrite the override code of our method, so we will postpone it for a moment and after the call, we will restore it (see - (void)invokeWithTarget:(id)target superclass:(Class)type ). - To create a C function with a return type of the right size to match our arbitrary IMP return value, we look again at the method signature, this time to determine the size of our return value. We perform another series of conditional expressions to check the size against known sizes and for each known size, we create a C function with a size that is hardcoded as the return value of the function. I created support for returning dimension values. To facilitate this, we use
imp_implementationWithBlock . (See PearlForwardIMP )
source share