http://forgecode.net/2010/02/simplifying-nsnumberformatter-use-on-the-iphone-with-objective-c-categories/


Simplifying NSNumberFormatter use on the iPhone with Objective-C categories

Tuesday 2nd February 2010Something that comes up regularly in iPhone development is the need to convert ​​NSNumber​​objects to ​​NSString​​ objects and vice versa. For converting an ​​NSNumber​​ to an ​​NSString​​, there is the ​​-stringValue​​ method, which is useful if you want just the plain number without any extra formatting. This isn’t particularly useful for presenting numbers in a user interface however, since ​​-stringValue​​ doesn’t give you any control over how the number is formatted.


Enter NSNumberFormatter

The ​​NSNumberFormatter​​ class is Cocoa’s way of converting between ​​NSNumber​​ and ​​NSString​​objects. It provides both printing (number-to-string) and parsing (string-to-number).​​NSNumberFormatter​​ offers a number of built-in “formatter styles”; these are pre-built styles that allow you to print or parse numbers in a number of common styles. The different styles are declared in an ​​enum​​ as the type: ​​NSNumberFormatterStyle​​. Here are the different formatter styles, and the result when converting ​​[NSNumber numberWithDouble:123.4]​​ into an ​​NSString​​.

  • NSNumberFormatterNoStyle (123)
  • NSNumberFormatterDecimalStyle (123.4)
  • NSNumberFormatterCurrencyStyle ($123.40)
  • NSNumberFormatterPercentStyle (12,340%)
  • NSNumberFormatterScientificStyle (1.234E2)
  • NSNumberFormatterSpellOutStyle (one hundred and twenty-three point four)

Note that my iPhone and Mac have the country set to Australia, and the language set to Australian English. These built-in styles are localised, so the results may be different depending on the country and language of your iPhone (or Mac, if you’re running in the iPhone Simulator).

NSNumberFormatter In Practice

To actually convert an ​​NSNumber​​ to an ​​NSString​​ using ​​NSNumberFormatter​​ using one of the pre-defined ​​NSNumberFormatterStyle​​ options (in this case, ​​NSNumberFormatterCurrencyStyle​​), you would use the following code:

​​NSNumberFormatter​​​ *formatter = [[​​NSNumberFormatter​​ alloc] init];
formatter.numberStyle = NSNumberFormatterCurrencyStyle;
​​NSString​​ *string = [formatter stringFromNumber:number];

[formatter release];

Simple enough, but every time I write code that does this, a little voice in the back of my head says:

You repeat those 4 lines of code all the time. Write a category on NSNumber, stick those 4 lines in a category method, beautify your code, and save the world!

Right. The following methods could be written in a category on ​​NSNumber​​, and the world would be a much better place:

- (​​NSString​​ *)stringWithNumberStyle:(NSNumberFormatterStyle)style;

+ (​​NSNumber​​​ *)numberWithString:(​​NSString​​ *)string
numberStyle:(NSNumberFormatterStyle)style;
Then we could do the conversion mentioned earlier as easily as this:

​​NSString​​ *string = [number stringWithNumberStyle:NSNumberFormatterCurrencyStyle];

We would have turned 4 lines of code into 1 easy to read line of code, which is clearly a lot nicer than the “normal” way of using an ​​NSNumberFormatter​​.

Then another voice in my head says:

But that means we’ll be creating, configuring and destroying an ​​NSNumberFormatter​​every time we use these methods! That’ll be inefficient, right?

Instead of seeking medication for the voices I seem to be hearing in my head, I decided to write up the categories and do some testing to see just how inefficient it would be.

I ended up with 4 different versions of my category methods. I wrote a test app that timed how long it took to perform the same conversions with each version, then collected the data to compare the efficiency of each version.

The Benchmark

To calculate the “efficiency” of each of my different approaches, I needed a “best practice” benchmark. I used code resembling the following to do a large number of conversions in the most efficient way possible:

​​NSNumberFormatter​​​ *formatter = [[​​NSNumberFormatter​​ alloc] init];
formatter.numberStyle = NSNumberFormatterPercentStyle;
for (int i = 0; i < TEST_ITERATIONS; i ++) {
​​NSNumber​​​ *num = [​​NSNumber​​ numberWithInt:i];
​​NSString​​ *str = [formatter stringFromNumber:num];
}

[formatter release];

Testing this code with ​​TEST_ITERATIONS​​ equal to 10,000 revealed that on average, each number-to-string conversion took approximately 113.7µs on my iPhone 3GS. This was my benchmark – now for the alternative methods.

Version 1 – Dumb

Version 1 is the most simple (and obvious) way to solve this problem:

- (​​NSString​​ *)stringWithNumberStyle1:(NSNumberFormatterStyle)style
{
​​NSNumberFormatter​​​ *formatter = [[​​NSNumberFormatter​​ alloc] init];
formatter.numberStyle = style;
​​NSString​​ *string = [formatter stringFromNumber:self];
[formatter release];
return string;
}

To do the same conversions as the benchmark, I used code like this:

for (int i = 0; i < TEST_ITERATIONS; i ++) {
​​NSNumber​​​ *num = [​​NSNumber​​ numberWithInt:i];
​​NSString​​ *str = [num stringWithNumberStyle:NSFormatterPercentStyle];
}

The obvious inefficiency here is that every time we call the method, we create, configure, then destroy an ​​NSNumberFormatter​​ object. If we’re using this method a lot, this might start to add up.

Testing showed that this method took 2741.1µs per conversion, which is around 24 times slower than the benchmark. If you need to do more than 100 of these conversions at a time, this adds up to a quarter of a second, which is definitely a noticeable delay (this compares to only 10ms for the benchmark). I decided once I saw these figures that it was worth optimising the code to see how fast we could get it to go.

Version 2 – Smart?

Version 2 caches a single ​​NSNumberFormatter​​ object in a global variable (effectively a singleton), so we don’t have to create and destroy an object every time we use our category method.

​​NSNumberFormatter​​ *sharedNumberFormatter = nil;
​​NSString​​ *kSharedNumberFormatterLock = @"kSharedNumberFormatterLock";

...

- (​​NSString​​ *)stringWithNumberStyle2:(NSNumberFormatterStyle)style
{
​​NSString​​ *string = nil;
@synchronized(kSharedNumberFormatterLock) {
if (sharedNumberFormatter == nil) {
sharedNumberFormatter = [[​​NSNumberFormatter​​ alloc] init];
}
sharedNumberFormatter.numberStyle = style;
string = [sharedNumberFormatter stringFromNumber:self];
}
return string;
}

This time, the only difference between our category method and the benchmark is that we have to run through a mutex lock every time, and we are re-configuring the​​NSNumberFormatter​​ every time we use it. How much does this slow us down? It turns out that this code averages 1466.2µs per conversion, which works out to be nearly twice as fast as version 1, but still 13 times slower than the benchmark.

Version 3 – Smarter

It turns out that re-configuring the ​​NSNumberFormatter​​ is slow. By maintaining a separate​​NSNumberFormatter​​ for each of the ​​NSNumberFormatterStyle​​ styles, our efficiency starts to approach the benchmark.

​​NSNumberFormatter​​ *sharedNumberFormatterDecimalStyle = nil;
​​NSNumberFormatter​​ *sharedNumberFormatterCurrencyStyle = nil;
...

static ​​NSString​​ *kSharedNumberFormatterDecimalStyleLock = @"kSharedNumberFormatterDecimalStyleLock";
static ​​NSString​​ *kSharedNumberFormatterCurrencyStyleLock = @"kSharedNumberFormatterCurrencyStyleLock";
...

- (​​NSString​​ *)stringWithNumberStyle3:(NSNumberFormatterStyle)style
{
​​NSNumberFormatter​​ *formatter = nil;
switch (style) {
case NSNumberFormatterDecimalStyle:
@synchronized(kSharedNumberFormatterDecimalStyleLock) {
if (sharedNumberFormatterDecimalStyle == nil) {
sharedNumberFormatterDecimalStyle = [[​​NSNumberFormatter​​ alloc] init];
sharedNumberFormatterDecimalStyle.numberStyle = NSNumberFormatterDecimalStyle;
}
}
formatter = sharedNumberFormatterDecimalStyle;
break;

case NSNumberFormatterCurrencyStyle:
@synchronized(kSharedNumberFormatterCurrencyStyleLock) {
if (sharedNumberFormatterCurrencyStyle == nil) {
sharedNumberFormatterCurrencyStyle = [[​​NSNumberFormatter​​ alloc] init];
sharedNumberFormatterCurrencyStyle.numberStyle = NSNumberFormatterCurrencyStyle;
}
}
formatter = sharedNumberFormatterCurrencyStyle;
break;

...

default:
break;
}
return [formatter stringFromNumber:self];
}

Note that I have removed the other styles from this code to aid in readability; where you see ‘…’, there is in fact duplicate code for the remaining formatter styles.

Version 3 clocks in at 127.8µs per conversion, which is only 12.4% longer than the benchmark. This is what we would expect – the only things that should be slowing us down (explaining the 12% speed penalty) are the mutex locks.

Version 4 – Smartest!

In version 4, I added an if statement that skips the mutex lock if the appropriate sharedNumberFormatter has already been initialised. An ​​if​​​ statement is a whole lot faster than a ​​@synchronized​​ statement, so this is another win.

- (​​NSString​​ *)stringWithNumberStyle4:(NSNumberFormatterStyle)style
{
​​NSNumberFormatter​​ *formatter = nil;
switch (style) {
case NSNumberFormatterDecimalStyle:
if (sharedNumberFormatterDecimalStyle) {
formatter = sharedNumberFormatterDecimalStyle;
break;
}
@synchronized(kSharedNumberFormatterDecimalStyleLock) {
if (sharedNumberFormatterDecimalStyle == nil) {
sharedNumberFormatterDecimalStyle = [[​​NSNumberFormatter​​ alloc] init];
sharedNumberFormatterDecimalStyle.numberStyle = NSNumberFormatterDecimalStyle;
}
}
formatter = sharedNumberFormatterDecimalStyle;
break;

...

default:
break;
}
return [formatter stringFromNumber:self];
}

Again, I’ve removed the code that handles the other formatter styles for readability (I only included the switch/case for the Decimal Style). This version clocks in at 116.3µs, which is only 1.02 times as long as the benchmark (2.3% to be precise). There’s clearly little point in trying to optimise this any further – this is about as fast is we’re going to get.

Number-to-String results

Here are the results for Number-to-Strong conversions. I generated these figures by running the tests multiple times, discarding any obvious outliers, then taking averages. If anyone is interested in seeing the code used to generate these numbers, let me know and I’ll post the whole Xcode project.

Average (µs)

Penalty (%)

Benchmark

113.7

-

Version 1

2741.1

2311%

Version 2

1466.2

1190%

Version 3

127.8

12.4%

Version 4

116.3

2.3%

String-to-Number results

Writing string-to-number methods was relatively straightforward after writing the number-to-string methods. The actual structure of each method was exactly the same, so the hard work was already done.

I also generated results for the string-to-number conversions:

Average (µs)

Penalty (%)

Benchmark

348.2

-

Version 1

2909.2

735.5%

Version 2

1664.7

378.1%

Version 3

369.0

5.9%

Version 4

359.7

3.3%

Conclusion

If you want to use any of the pre-built ​​NSNumberFormatterStyle​​ options, the ​​NSNumber​​ category methods that I have described here give you a much nicer syntax, with a negligible performance hit. If you need to use a non-standard formatter style, then clearly these methods won’t help, and you’ll need to use an ​​NSNumberFormatter​​ directly. Creating, configuring and destroying each ​​NSNumberFormatter​​ instance is expensive compared to the time it takes to actually do the conversion; if you ever have to use an ​​NSNumberFormatter​​with the same settings more than a couple of times, hold on to it (e.g. in an ivar).

Download

I’ve uploaded the source code for all 4 versions for both ​​-stringWithNumberStyle:​​ and​​+numberWithString:numberStyle:​​. I will publish a polished version of the “Version 4″ methods on GitHub once I’ve given the code a proper test-drive.