Why does NSInvocation getReturnValue: lose object?
Asked Answered
M

5

3

I need your help. I have some problems with NSInvocation 'getReturnValue:' method. I want to create UIButton programmatically, and even more, I want to create it dynamically using NSInvocation and through passing value through the NSArray (that's why I wrapped UIButtonTypeRoundedRect).

Listing.

NSLog(@"Button 4 pushed\n");//this code executed when button pushed
Class cls = NSClassFromString(@"UIButton");//if exists {define class},else cls=nil
SEL msel = @selector(buttonWithType:);
//id pushButton5 = [cls performSelector:msel withObject:UIButtonTypeRoundedRect];//this code works correctly,but I want to do this by NSInvocation
//---------------------------
NSMethodSignature *msignatureTMP;
NSInvocation *anInvocationTMP;

msignatureTMP = [cls methodSignatureForSelector:msel];
anInvocationTMP = [NSInvocation invocationWithMethodSignature:msignatureTMP];
[anInvocationTMP setTarget:cls];
[anInvocationTMP setSelector:msel];

UIButtonType uibt_ = UIButtonTypeRoundedRect;
NSNumber *uibt = [NSNumber numberWithUnsignedInt:uibt_];
NSArray *paramsTMP;
paramsTMP= [NSArray arrayWithObjects:uibt,nil];

id currentValTMP = [paramsTMP objectAtIndex:0];//getParam from NSArray
NSInteger i=2;
void* bufferTMP;

//if kind of NSValue unwrapp it.
if ([currentValTMP isKindOfClass:[NSValue class]]) {
    NSUInteger bufferSize = 0;
    NSGetSizeAndAlignment([currentValTMP objCType], &bufferSize, NULL);
    bufferTMP = malloc(bufferSize);
    [currentValTMP getValue:bufferTMP];//copy currentVal to bufer
    [anInvocationTMP setArgument:bufferTMP atIndex:i];// The +2 represents the (self) and (cmd) offsets
}else {
    [anInvocationTMP setArgument:&currentValTMP atIndex:i];//Again,+2 represents (self) and (cmd) offsets
}
void* result = malloc([[cls methodSignatureForSelector:msel] methodReturnLength]);
[anInvocationTMP invoke];
[anInvocationTMP getReturnValue:result];

NSLog(@"sizeof(UIButton)=%i,sizeof(result)=%i,methodreturnlength = %i,sizeof(*result)=%i",class_getInstanceSize(NSClassFromString(@"UIButton")),sizeof(result),[[cls methodSignatureForSelector:msel] methodReturnLength],sizeof(*result));

id pushButton5;
pushButton5=result;
//---------------------------

NSLog output: sizeof(UIButton)=140,sizeof(result)=4,methodreturnlength = 4,sizeof(*result)=1

The problem is that value from NSInvocation is pointer of size 4 bytes. It should point to UIButton object,size of 140 bytes. But actually refers to 1 byte data. So what does happen with UIButton object,that should be initialized by 'buttonWithType:'?

Added after getting some answers:

To clarify: I want to get UIButton object, but after this code id pushButton5 = (id) result; ,when I try to work with pushButton5,it causes EXC_BAD_ACCESS. Can someone help me?

Can this happen because of this?

Class cls = NSClassFromString(@"UIButton");
...
[anInvocationTMP setTarget:cls];

It is correct, isn't it?

Menis answered 16/8, 2011 at 12:20 Comment(0)
M
1

result has type void* and your sizeof(*result) expression is measuing sizeof(void), which apparently yields 1 in your compiler.

To check type of an Objective-C object, use isKindOfClass: method:

id resObj = (id)result;
if ([resObj isKindOfClass:[UIButton class]])
    NSLog(@"mazel tov, it's a button");

Just be sure it's really an objective-c object first.

Mayo answered 16/8, 2011 at 12:49 Comment(2)
Thank you. Yes I wanted to know if result type of UIButton,but when I use resObj or in my case pushButton5,it causes EXC_BAD_ACCESS.Menis
Probably the method does not return an Obj-C object. Try checking methodReturnType.Mayo
M
18

If you're using ARC, I would replace this:

void* result = malloc([[cls methodSignatureForSelector:msel] methodReturnLength]);
[anInvocationTMP invoke];
[anInvocationTMP getReturnValue:result];

With this:

CFTypeRef result;
[anInvocationTMP invoke];
[anInvocationTMP getReturnValue:&result];
if (result)
    CFRetain(result);
UIButton *pushButton5 = (__bridge_transfer UIButton *)result;

The reason is that the invokation's return object is not retained, so it will go away, even if you immediately assign it to an object reference, unless you first retain it and then tell ARC to transfer ownership.

Moynahan answered 19/7, 2012 at 21:2 Comment(5)
This worked for me but I had to check that result was not nil before calling CFRetain.Whipstitch
Damn, this is that kind of things I hate ARC for sometimes :< Such a mess.Staging
One practice I've taken up is to avoid NSInvocation and instead use blocks. The code is cleaner, there's less futzing about making ARC happy, and it's more flexible.Moynahan
@spstanley, thank you!! - spent the last two hours trying to track this down!Feminacy
@spstanley, you are my hero!Tuesday
M
1

result has type void* and your sizeof(*result) expression is measuing sizeof(void), which apparently yields 1 in your compiler.

To check type of an Objective-C object, use isKindOfClass: method:

id resObj = (id)result;
if ([resObj isKindOfClass:[UIButton class]])
    NSLog(@"mazel tov, it's a button");

Just be sure it's really an objective-c object first.

Mayo answered 16/8, 2011 at 12:49 Comment(2)
Thank you. Yes I wanted to know if result type of UIButton,but when I use resObj or in my case pushButton5,it causes EXC_BAD_ACCESS.Menis
Probably the method does not return an Obj-C object. Try checking methodReturnType.Mayo
S
0

The return value is a UIButton* not a UIButton. Thus everything looks fine in your code.

Sociality answered 16/8, 2011 at 12:25 Comment(1)
I didn't tell that buttonWithType: returns object. I mean that when I tried to get the object this way: (*result), the size didn't equal the size of UIButton. But as Yuji sad "...sizeof() doesn't know what result points to at the runtime".Menis
T
0

It's not a problem.

First, getReturnValue: should come after the invocation. So,

[anInvocationTMP getReturnValue:result];
[anInvocationTMP invoke];

should be

[anInvocationTMP invoke];
[anInvocationTMP getReturnValue:result];

Next, you should forget about the size of UIButton itself. What buttonWithType returns is UIButton*, not UIButton. In Objective-C, you should never directly deal with the object itself. You should always work with a pointer to the object.

Finally, (Objective-)C's sizeof operator is purely compile-time operation. So, sizeof(*result) doesn't know at all what result points to at the run time. But it doesn't matter... you shouldn't care about the size of UIButton, as I already told you.

Therewith answered 16/8, 2011 at 12:31 Comment(1)
Thanks I fixed it.The point is that after this code id pushButton5 = (id) result; ,when I try to work with pushButton5,it causes EXC_BAD_ACCESS. That's why I need to check the size.Menis
M
0

Really answer that I needed was... How do you think where ? Yes, in documentation.

  • Use the NSMethodSignature method methodReturnLength to determine the size needed for buffer :

    NSUInteger length = [[myInvocation methodSignature] methodReturnLength];
    buffer = (void *)malloc(length);
    [invocation getReturnValue:buffer];
    
  • When the return value is an object, pass a pointer to the variable (or memory) into which the object should be placed :

    id anObject;
    NSArray *anArray;
    [invocation1 getReturnValue:&anObject];
    [invocation2 getReturnValue:&anArray];
    

So the problem solved. Thanks for your respond guys.

Menis answered 17/8, 2011 at 7:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.