http://forgecode.net/2010/02/simplifying-nsnumberformatter-use-on-the-iphone-with-objective-c-categories/
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.
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
.
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).
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:
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 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.
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 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 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 theNSNumberFormatter
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.
It turns out that re-configuring the NSNumberFormatter
is slow. By maintaining a separateNSNumberFormatter
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.
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 (%) | |
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% |
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% |
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 -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.