Going From C# To Objective-C: Boxing and Unboxing

Speaking from experience, .Net has a tendency to spoil us developers. In this post i'm going to talk about one of those scenarios, Boxing/Unboxing. If you're a C# programmer, and you have no idea what i mean by Boxing/Unboxing that proves my point :).  

For those of you who are unfamiliar with these terms:

Boxing: Taking a value type (int, float, bool, struct, etc...) and placing it into a reference type container such as an object. 

object obj = 234;

Unboxing: Taking that boxed reference type (int, float, bool struct, etc..) and casting it back to its actual value type.

int i = (int)obj

Now, .Net does this automatically for you. So, you've probably done these operations lots of times and never even realized it. However, its worth noting that these operations are expensive. Under the hood when you box an object, it actually creates a new object to contain your value type. So although its nice that you don't have to think about it, its also awful b/c if you don't think about it you can really slow down the performance in your app. 

Objective-C

In objective-c its a bit different. You can't explicitly put a value type into a reference type variable. For example:

id obj = 234;

That line of code will not compile in objective-c. Instead, you have to do the boxing yourself. 

id obj = [NSNumber numberWithInt:234];
or
id obj = @234; // Note this is a new shortcut added in XCode 4.4

So, unlike C#, Obj-C is making us do the boxing ourselves. To do that, they've provided the following two classes: NSValue and a derivative of NSValue called NSNumber. There are also a bunch of extensions added to NSValue that handle structs such as CGRect and CGPoint. 

Reasons to Box/Unbox

I'm sure you're asking yourself: "Why do I even care about this? I don't have any reason to box my value types."

However, you'll probably run into this scenario more than you think. I'll give you an example:

Example:

Lets say you have a need for an Array or Dictionary of numbers. Thats not uncommon at all.  However, in objective-c both arrays and dictionaries require that the types within them be value types. Which means the only way to get a number in your array is to box it. 

NSArray* array = [NSArray arrayWithObjects:[NSNumber numberWithInt:20], [NSNumber numberWithInit:40], nil];
or
NSArray* array = @[@20, @40]; // Gotta love shortcuts!!

This means that when you loop through this array later on, you're going to be dealing with NSNumbers and not ints. 

So of course that means you'll have to unbox it. 

NSNumber* num = array[0];
int x = num.intValue;

Reflection

Did you know there is actually a scenario in which Objective-C will box a type for you? It happens when you use reflection to access a property that is a value type. 

For example, if I had a class called Person, and on that class i had a property called "age" that was of type "int". I could access that property through reflection and get an NSNumber back. 

@interface Person
 @property(nonatomic, assign)int age;
@end
Person* p = [[Person alloc]init];
p.age = 22;
 
NSNumber* age = [p valueForKey:@"age"];

And, it will automatically unbox it if you use reflection to set the value:

[p setValue:@30 forKey:@"age"];

Although this is definitely an edge case, its handy tick to know in case you're writing some kind of application that requires you to work with objects that you don't know about until runtime. Otherwise, you'd have to know the explicit type that you're working with in order to box it properly. 

Problem Areas

Another common scenario is where you invoke a method through it selector. While completely valid, if you have a parameter that is a value type, it won't work. 

-(void)test:(int)x
{
  // Do Something  
}
 
-(void)buttonClick
{
   [self performSelector:@selector(test:) withObject:@22];
}

While the code above will compile and run. The value passed into "test:' will not be 22. Instead its actually the address of the NSNumber. So in this case it's actually not posible to invoke test indirectly. 

instead you have to invoke it directly. Which may, or may not be a problem depending on your scenario. The only way to work around this issue, is to instead change the parameter type of test: to an NSNumber, or if you don't have access to the method you're calling, then have a pass through method that will do the unboxing for you and call test: directly. 

As always, i hope this article was helpful. 

By Stephen Zaharuk (SteveZ)


Tags / ,