4

Struggling with this one. Hoping it's possible and I don't sound silly.

I'm hacking forwardInvocation in a class I'm writing. What I want to do is forward the invocation to one selector or another depending on if it is an object or primitive type. The end goal is I want to "box" the primitives so they can be added to arrays/dictionaries. For simplicity, the two types of values that typically come through here are NSStrings and enums.

In short, given a pointer, is there a way to tell if it is an object?

__unsafe_unretained id argument;
[anInvocation getArgument:&argument atIndex:2];

// EXC_BAD_ACCESS if primitive (i.e. NSInteger value of 2 ($1 = 0x00000002) )
if (![argument isKindOfClass:[NSObject class]]) {
    // Box the value
    ...
}

Is there a test I can run? Right now my code is hackishly doing this nasty trick:

// All my enums have at most 10 elements. I'm so bad at code.
if ((NSInteger)argument < 10) {
    // Box the value
    ...
}

Thanks in advance.

Erik Kerber
  • 5,646
  • 7
  • 38
  • 56
  • 1
    Not only do you not know if `argument` is an object pointer or an integer or something, you don't even know if `argument` contains the whole argument, because it might not even be the right size for the parameter. If the parameter had a huge size, for example a struct, then when you do `getArgument:` it would write into memory starting at the location of `argument`, and continuing for the size of that parameter type, which if it is bigger than the size of a pointer, will overwrite other stuff on the stack. – newacct Feb 19 '14 at 10:30

2 Answers2

3

You can get the type from the method signature:

NSMethodSignature *signature = [invocation methodSignature];
const char* argType = [signature getArgumentTypeAtIndex:2];

The types are listed under Type Encodings in the Objective-C Runtime Guide

You should also make sure you know the type before calling getArgument:atIndex::

This method copies the argument stored at index into the storage pointed to by buffer. The size of buffer must be large enough to accommodate the argument value.

__unsafe_unretained id argument;
[anInvocation getArgument:&argument atIndex:2];

This will write past argument on the stack if the size of the actual argument is greater than sizeof(id)

Sebastian
  • 7,670
  • 5
  • 38
  • 50
  • What help will that be, if the method is declared as taking a `void*`? He's deliberately hidden the facts from himself. – matt Feb 19 '14 at 05:10
  • 1
    I thought he was talking about the `void*` in `- (void)getArgument:(void *)buffer atIndex:(NSInteger)index` – Sebastian Feb 19 '14 at 05:11
  • Maybe. Hard to tell. I guess I was thinking that if it was as easy as consulting the method signature he would have done that. – matt Feb 19 '14 at 05:12
  • You are both right. The method signature does not contain anything useful to me. To be totally honest, to trigger the forwardInvocation I just supplied a dummy method signature inside methodSignatureForSelector. – Erik Kerber Feb 19 '14 at 05:16
  • @ErikKerber: What exactly are you trying to do? – Sebastian Feb 19 '14 at 05:16
  • @Sebastian Was just a crazy concept I was doing that was similar to a dynamic type in .NET. An object that would listen to any set/get message sent to it, and store/retrieve that value. – Erik Kerber Feb 19 '14 at 05:21
  • @Sebastian If you're curious: https://gist.github.com/eskerber/9086494. Be kind :) – Erik Kerber Feb 19 '14 at 05:25
  • 3
    @ErikKerber But the thing is that once you've disguised something as `void*` it is absolutely disguised. That is what `void*` means: you have thrown away all knowledge of what this pointer is pointing to. You have to know in some other way. – matt Feb 19 '14 at 05:28
  • 2
    @ErikKerber dynamic generation of setters / getters is perfectly possible. After all, that is how `@dynamic` works (e.g. in Core Data). But that is not done the way you are trying to do it. You should look into `resolveInstanceMethod:`. – matt Feb 19 '14 at 05:35
  • @ErikKerber: In order to even *compile* a function call or method call in C/Objective-C, the compiler needs to know the exact function signature (all parameter types and return type). So the calling code knew the signature it was calling at compile time. Your issue is how to know the signature in `forwardInvocation:` at runtime. A good question to ask is where the signature at compile time came from. – newacct Feb 19 '14 at 10:26
  • 1
    @ErikKerber: Plus, in your `forwardInvocation:`, you have an `NSInvocation`, which contains a method signature, which came from `methodSignatureForSelector:`, which you said returns a dummy signature. So an even bigger problem is that your `NSInvocation` arguments are garbage, because it was built using a garbage signature. If the signature contained too few parameters, it will be missing arguments; if the signature contained too many parameters, it will have garbage values from the stack; if the signature has incorrect types, it may have arguments split into other arguments in horrible ways. – newacct Feb 20 '14 at 08:19
3

Pointers in C are just values that represent addresses. The thing at the address pointed to by a void pointer is explicitly untyped. If the goal is to have a method that can take any type — object pointer, scalar or composite — that just isn't going to work. And besides the impossibility of recovering a type from a void pointer, if you're literally trying to pass in a scalar directly instead of passing in its address, that is doubly impossible because the compiler needs to know right type of the value in order to emit the correct code, and most types cannot be converted to a pointer with any fidelity. Either way, Objective-C's type system is just not powerful enough to do what you want.

Chuck
  • 234,037
  • 30
  • 302
  • 389