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?

Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149
Alexander
  • 1,228
  • 2
  • 15
  • 29

5 Answers5

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.

spstanley
  • 940
  • 7
  • 14
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.

hamstergene
  • 24,039
  • 5
  • 57
  • 72
  • 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. – Alexander Aug 17 '11 at 05:06
  • Probably the method does not return an Obj-C object. Try checking `methodReturnType`. – hamstergene Aug 17 '11 at 05:19
0

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

Joshua Weinberg
  • 28,598
  • 2
  • 97
  • 90
  • 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". – Alexander Aug 17 '11 at 04:40
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.

Yuji
  • 34,103
  • 3
  • 70
  • 88
  • 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. – Alexander Aug 17 '11 at 05:29
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.

Jonathan F.
  • 2,357
  • 4
  • 26
  • 44
Alexander
  • 1,228
  • 2
  • 15
  • 29