Something 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.
NSNumberFormatter class is Cocoa’s way of converting between
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
- 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
NSNumberFormatter using one of the pre-defined
NSNumberFormatterStyle options (in this case,
NSNumberFormatterCurrencyStyle), you would use the following code:
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:
Then we could do the conversion mentioned earlier as easily as this:
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
Then another voice in my head says:
But that means we’ll be creating, configuring and destroying an
NSNumberFormatterevery 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.
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:
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:
To do the same conversions as the benchmark, I used code like this:
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.
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.
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.
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.
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 (%)|
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 (%)|
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).
I’ve uploaded the source code for all 4 versions for both
+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.