1
Mar
2008
 

Does Objective-C Perform Autoboxing on Primitives?

by Marcus Zarra


This article is inaccurate.


The writer was smoking crack or something when he wrote it and has not been able to duplicate his tests since. This article is left here for historical reasons.

One of the things about Objective-C that I find extremely useful is the ability to resolve a method call at runtime. In addition this same functionality allows us to do some fairly creative things with callbacks, passing messages between threads, etc.

However there is a bit of a trick when it comes to passing primitives though some of these methods. For example, one method that I use quite frequently is performSelectorOnMainThread:withObject:waitUntilDone:. How exactly does one pass a BOOL or an int to this method?

Fortunately, this is actually very simple. While Objective-C does not have the ability to autobox primitives, it will unbox them in this situation. For example, if you had the following method:

- (void)updateCounter:(int)counter
{
    //Do something clever
}

And you needed to call it from a background thread but it needed to be performed on the main thread, all you need to do is:

-(void)aMethodOnABackgroundThread
{
    int myInt = 12;
    [self performSelector:@selector(updateCounter:)
               withObject:[NSNumber numberWithInt:myInt]
            waitUntilDone:NO];
}

As you can see, we are passing a NSNumber object into the method. So what is the method actually receiving? It will receive a primitive int just as it expects to. No additional work is required in the receiving method.

This same unboxing can be performed with any primitive including BOOL. Simply wrap them in a NSNumber and pass the NSNumnber through the method call.

As always, there is an exception to the rule. When you are building your own NSInvocation (the magic Class behind these callbacks) you do not need to pass it an object for a primitive.

-(void)aMethodOnABackgroundThread
{
    int myInt = 12;
    SEL sel = @selector(updateCounter:);
    NSMethodSignature *sig = [self methodSignatureForSelector:sel];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:];
    [invocation setTarget:self];
    [invocation setSelector:sel];
    [invocation setArgument:&myInt atIndex:2];
    [invocation invoke];
}

In this example, the NSNumber wrapper/boxing is unnecessary as the invocation accepts anything.

Comments

nevyn says:

Thanks! Did not know that.

I have my own NCCommon.h that I can’t live without. While we don’t have automatic boxing, very short convenience method at least makes the code palatable. Behold:

extern NSString* s(const char *str);
#define sf(format, …) ([NSString stringWithFormat: format, ##__VA_ARGS__])
extern NSArray *ary(id item, …);
extern NSDictionary *dict(id key, id value, …);
extern NSNumber *num(double n);

and NCCommon.m:
NSString* s(const char *str) {
return [NSString stringWithUTF8String:str];
}
NSArray *ary(id item, …) {
if(!item) return [NSMutableArray array];
NSMutableArray *ar = [NSMutableArray arrayWithObject:item];

va_list va;
va_start(va, item);
id obj = va_arg(va, id);
while(obj != nil) {
[ar addObject:obj];
obj = va_arg(va, id);
}
va_end(va);
return ar;
}

NSDictionary *dict(id key, id value, …) {
if(!key) return [NSMutableDictionary dictionary];
NSMutableDictionary *mdict = [NSMutableDictionary dictionaryWithObject:value forKey:key];

va_list va;
va_start(va, value);
id nextkey = va_arg(va, id);
id val = va_arg(va, id);
while(nextkey != nil && val != nil) {
[mdict setObject:val forKey:nextkey];
nextkey = va_arg(va, id);
val = va_arg(va, id);
}
va_end(va);
return mdict;
}

NSNumber *num(double n) {
return [NSNumber numberWithDouble:n];
}

(yes, s and num could very well be defines as well… I blame it on being old code.)

Andy Kim says:

Very nice! I wish I had known this sooner.

Wevah says:

This is good to know. I should be able to remove a few lines from some stuff now…

Wevah says:

Actually, in my limited testing, I can’t seem to get this to work. I’m calling the -incrementBy: method of an NSProgressIndicator, and it doesn’t increment.

Marcus Zarra says:

Wevah,

Can you post what you are doing or send me an email with the lines of code you are working with?

mzarra at mac dot com

Wevah says:

It’s basically just:

[progressIndicator performSelectorOnMainThread:@selector(incrementBy:) withObject:[NSNumber numberWithDouble:someDouble] waitUntilDone:YES]; // or waitUntilDone:NO, neither work for me

Marcus Zarra says:

I do not see anything wrong with that code. I would ask:

–is progressIndicator != nil?
–what happens when you call it directly without performSelectorOnMainThread? You can probably call this from the background thread to test without the world coming to an end.

That would be my suggestion because the auto-unboxing discussed above definitely works. I use it all over the place in iWeb Buddy.

Wevah says:

Yeah, I currently call it from a background thread as-is, with no apparent ill effects, so I’ll probably just leave as it is. I’ll definitely test this out more as appropritate; it’s no show-stopper for me, by any means.

markd says:

I can’t get this to work either. Here’s a little app that sends an NSNumber* to a method that takes an int via performSelectorOnMainThread. (you’ve got a little typo in the sample code, “performSelector:” rather than “performSelectorOnMainThread:”). The NSLog seems to show that I’m getting the address of the NSNumber rather than the unboxed value. This is on Leopard 10.5.2.

Here’s the program:

#import

/* compile with
gcc -g -Wall -framework Foundation -o autounbox autounbox.m
*/

@interface Receiver : NSObject

– (void) printGroovyness: (int)groovy;

@end // Receiver

@implementation Receiver

– (void) printGroovyness: (int)groovy {
NSLog (@”Groovy received: %x”, groovy);
} // printGroovyness

@end // Receiver

@interface Sender : NSObject
@end // Sender

@implementation Sender

– (void) boxingDay: (Receiver *)receiver {
[[NSAutoreleasePool alloc] init];

int value = random() % 300;
NSNumber *number = [NSNumber numberWithInt: value];

NSLog(@”main-threading groovy of %d with pointer %p”, value, number);

[receiver performSelectorOnMainThread: @selector(printGroovyness:)
withObject: number
waitUntilDone: NO];

sleep(10);

} // boxingDay

@end // Sender

int main (int argc, const char *argv[]) {
[[NSAutoreleasePool alloc] init];

Sender *sender = [[Sender alloc] init];
Receiver *receiver = [[Receiver alloc] init];

[NSThread detachNewThreadSelector: @selector(boxingDay:)
toTarget: sender
withObject: receiver];

// Give the runloop marklar a chance to catch its breath
sleep (1);
[[NSRunLoop mainRunLoop] run];

return (0);

} // main

and the output:

2008-03-14 17:58:07.068 autounbox[2288:1003] main-threading groovy of 283 with pointer 0x20a720
2008-03-14 17:58:08.069 autounbox[2288:807] Groovy received: 20a720

I’d love to know what I’m doing wrong, since this would simplify a primitive-type-passing situation I’m in.

Thanks!
++md

markd says:

And here’s a link to the code in case extracting it from the comments is annoying : http://borkware.com/hacks/autounbox.m

Rob Keniger says:

I can’t get this to work if I use an NSNumber. In my case I am calling a method that takes a BOOL argument:

-(void) makeTargetFieldFirstResponder:(BOOL) becomeFirstResponder

If I call it like this it fails to pass the becomeFirstResponder argument:

[self performSelector:@selector(makeTargetFieldFirstResponder:) withObject:[NSNumber numberWithBool:YES] afterDelay:duration];

However, if I do this it works fine:

[self performSelector:@selector(makeTargetFieldFirstResponder:) withObject:(id)YES afterDelay:duration];

I must say I’m not really comfortable with this, it seems like taking advantage of a fluke of the language implementation rather than being correct. It’s a shame the Apple documentation is so sparse in this area.

Marcus Zarra says:

Rob,

I definitely share your discomfort with passing in a BOOL that way. It is certainly not very pretty.

As for this post, I have been mulling over it for quite a while now. When I wrote it, I was 100% certain that it worked. However, since posting it, I have received several code samples and comments showing me that it does not in fact work.

I have been wrapped up with other projects and have not had a chance to give it the attention it is due. I suspect that I will be posting a retraction of this article in the near future.

drewmccormack says:

Hi Marcus,

I have done some testing of my own, and this does not work. I have also heard it does not work from people in Apple.
I suggest removing the story, or at the very least putting a big warning at the top that it is not accurate.
I started using this trick in my own code, only to find it was introducing subtle bugs.

Drew

Marcus Zarra says:

Yes, I need to do so, just have not had a chance to yet.