Is it possible that NSNumberFormatter leaks memory? - iphone

This code leaks when I send a non-numeric string, but doesn't when I send a numeric string. Is it possible that numberFromString: leaks memory when failing and returning nil?
- (BOOL)isNum:(NSString*)str
{
BOOL ans = YES;
NSNumberFormatter* nf = [[NSNumberFormatter alloc] init];
if ([nf numberFromString:str] == nil)
ans = NO;
[nf release];
return ans;
}

Yes, it is possible. It is fine when the parameter only containing letters, such as #"asdf" or only containing numbers, such as #"1234". It will leak, as the Instruments shown, when the parameter is the combination of letters and numbers, such as #"123asdf".

Related

Elegant method to omit fraction formatting number if number is an integer

I am formatting floating point numbers and right now I have the %0.2f formatter, but I'd like to omit the .00 if the floating point number is an even integer.
Of course I can think of string replacing the .00, but that's crude.
I found that the description of NSNumber also does something similar:
NSNumber *number = [NSNumber numberWithFloat:_paragraphSpacing];
[retString appendFormat:#"margin-bottom:%#px;", number];
This this does hover not limit the post comma digits. if the number is 1234.56789 then the description will output that.
So my question is, is there a just as simple way - possibly without having to create an NSNumber object - to achieve this result?
Since floating-point numbers aren't exact, there's no guarantee that your number will actually be an integer. You can, however, check if it's within a reasonably small distance from an integer value. And of course you don't need an NSNumber for this. (Generally speaking, NSNumber is not used for formatting, its purpose is representing a primitive C type, either integral or floating-point types, using an Objective-C object.)
#include <math.h>
- (NSString *)stringFromFloat:(float)f
{
const float eps = 1.0e-6;
if (abs(round(f) - f) < eps) {
// assume an integer
return [NSString stringWithFormat:#"margin-bottom: %.0fpx", round(f)];
} else {
// assume a real number
return [NSString stringWithFormat:#"margin-bottom: %.2fpx", f];
}
}
Use a formatter:
NSNumberFormatter* formatter= [NSNumberFormatter new];
formatter.numberStyle= NSNumberFormatterDecimalStyle;
formatter.maximumFractionDigits=2;
NSNumber *number = [NSNumber numberWithFloat:_paragraphSpacing];
[retString appendFormat:#"margin-bottom:%#;", [formatter stringFromNumber: number]];
You can use an NSNumberFormatter for this:
static NSNumberFormatter *numberFormatter = nil;
if (numberFormatter == nil) {
numberFormatter = [[NSNumberFormatter alloc] init];
numberFormatter.minimumFractionDigits = 0;
numberFormatter.maximumFractionDigits = 2;
numberFormatter.usesGroupingSeparator = NO;
}
NSString *formattedNumberString = [numberFormatter
stringForNumber:[NSNumber numberWithDouble: _paragraphSpacing]];
You can use C function modff to get the fraction part and test it:
float fractionPart = 0.;
modff(_paragraphSpacing, &fractionPart);
if( fabsf(fractionPart) < 0.01 ) {
// format as integer
[retString appendFormat:#"margin-bottom:%d", (int)_paragraphSpacing];
} else {
// format as float
[retString appendFormat:#"margin-bottom:%0.2f", _paragraphSpacing];
}

What is a better way to check if a string is an integer on iPhone?

The following code will identify if a string is an integer - that is, the string contains only digits. But, I hate this code. What is a better way?
NSString *mightBeAnInteger = fooString;
int intValue = [fooString intValue];
if (intValue > 0
&& [[NSString stringWithFormat:#"%d",intValue] isEqualToString:mightBeAnInteger]) {
NSLog(#"mightBeAnInteger is an integer");
}
[fooString rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]].location == NSNotFound will be YES if the string only has number characters in it.
Note that this doesn't catch negative numbers, so you'll have to add in the negative sign (probably by grabbing a mutableCopy and adding the sign).
-(BOOL) stringIsNumeric:(NSString *) str {
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSNumber *number = [formatter numberFromString:str];
[formatter release];
return !!number; // If the string is not numeric, number will be nil
}
source: Objective c: Check if integer/int/number

NSNumberFormatter iOS 3.2 Bug?

I have code that I am porting from iOS 4 to iOS 3.2 for a demo project on an iPad. I have this code:
+(int) parseInt:(NSString *)str
{
NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
[nf setAllowsFloats:NO];
[nf setMaximum:[NSNumber numberWithInt:INT_MAX]];
[nf setMinimum:[NSNumber numberWithInt:INT_MIN]];
#try {
NSNumber *num = [nf numberFromString:str];
if (!num)
#throw [DataParseException exceptionWithDescription:#"the data is not in the correct format."];
return [num intValue];
}
#finally {
[nf release];
}
}
This works spendid on iOS 4, throwing exceptions when a string (such as a date, which I am having problems with):
1/1/2010
For some reason, num isn't nil, it has the value 1, while on iOS 4, It is nil as expected. I was originally using NSScanner because it was easier than NSNumberFormatter to use, but I ran into the same problem, it doesn't parse the entire string, just the first number in the string.
Is there something I can do to fix this, or must I manually create an int parser. I would prefer not to use a C-Based approach, but if I must, I will.
EDIT: I have updated my code to this:
+(int) parseInt:(NSString *)str
{
NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
[nf setAllowsFloats:NO];
[nf setMaximum:[NSNumber numberWithInt:INT_MAX]];
[nf setMinimum:[NSNumber numberWithInt:INT_MIN]];
#try {
IF_IOS4_OR_GREATER
(
NSNumber *num = [nf numberFromString:str];
if (!num)
#throw [DataParseException exceptionWithDescription:#"the data is not in the correct format."];
return [num intValue];
)
else {
NSNumber *num = nil;
NSRange range = NSMakeRange(0, str.length);
NSError *err = nil;
[nf getObjectValue:&num forString:str range:&range error:&err];
if (err)
#throw [DataParseException exceptionWithDescription:[err description]];
if (range.length != [str length])
#throw [DataParseException exceptionWithDescription:#"Not all of the number is a string!"];
if (!num)
#throw [DataParseException exceptionWithDescription:#"the data is not in the correct format."];
return [num intValue];
}
}
#finally {
[nf release];
}
}
And I get a EXC_BAD_ACCESS signal when I try to parse the string 1/1/2001. Any Ideas?
(iOS 4 or greater is defined here: http://cocoawithlove.com/2010/07/tips-tricks-for-conditional-ios3-ios32.html)
I have a new error: when I parse the number, it isn't exact (exact as in it has multiple decimal points when using same code for floats) anymore.... how can I fix that?? (I might just use #joshpaul's answer...)
I couldn't find anything specific to iOS but the data formatting guide has this interesting paragraph:
Note: Prior to Mac OS v10.6, the implementation of getObjectValue:forString:errorDescription: would return YES and an object value even if only part of the string could be parsed. This is problematic because you cannot be sure what portion of the string was parsed. For applications linked on or after Mac OS v10.6, this method instead returns an error if part of the string cannot be parsed. You can use getObjectValue:forString:range:error: to get the old behavior; this method returns the range of the substring that was successfully parsed.
I would not be at all surprised if numberFromString: was implemented in terms of the above method and the iOS 3.2 NSNumberFormatter is based on 10.5 whereas the iOS 4 version is 10.6.
Just my guess.
If you pass 1/1/2010 on iOS 3.2 the 1 will be parsed and the rest ignored. You could test the hypothesis by seeing if you get 2 when you pass 2/1/2010.
The work around would seem to be to use getObjectValue:forString:range:error:.
So the basic [str intValue] doesn't work? Nor, [scanner scanInt:&int]?
What about using NSCharacterSet, i.e.:
NSString *test = [str stringByTrimmingCharactersInSet:[NSCharacterSet decimalDigitCharacterSet]];
if ([test length]) #throw ...;
return [str intValue];

variable parameter function - EXC_BAD_ACCESS when calling [obj release];

I have the following method:
(void)makeString:(NSString *)str1,... {
va_list strings;
NSString *innerText = [[NSString alloc] init];
NSString *tmpStr = [[NSString alloc] init];
if (str1) {
va_start(strings, str1);
while (tmpStr = va_arg(strings, id)) {
innerText = [innerText stringByAppendingString:tmpStr];
}
label.text = [str1 stringByAppendingString:innerText];
}
[tmpStr release];
}
I will eventually get to Objective C Memory Management reading, where I'm sure I will find the answer to this - probably related to pointers and copying - , but for now, can anyone explain why if I add [innerText release]; as the last line of this function, i get an EXC_BAD_ACCESS error at runtime?
First, your code is erroneous.
As far as I can see you are only concatenating the strings to assign the result to label.text.
I assume that label is an ivar, so label.text = … ist legal. Then the following should do it:
- (void)makeString: (NSString *)str1, ...
{
if (str1) {
NSString *tmpStr;
va_list strings;
va_start(strings, str1);
while (tmpStr = va_arg(strings, id)) {
str1 = [str1 stringByAppendingString: tmpStr];
}
label.text = str1;
}
}
Some notes:
You should not release any input parameter unless your method is about releasing something.
As the first answer stated, you should not release the result of stringByAppendingString: unless
you have retained it before.
[Update]
I changed the answer because it contained an error. label.text = str1 should retain str1 of course (if it wants to keep it). Especially the calling code should not retain str1 unless it wants to keep it for itself.
stringByAppendingString returns an autoreleased string, which is replacing your original assignment. So your release is not needed. But you are leaking memory with the two allocs above.
You should probably use [NSString initWithCString:va_arg(strings, id)] to assign the tmpStr too.

iPhone Currency input using NSDecimal instead of float

iPhone/Objective-C/Cocoa newbie here. Based on a number of posts on stackoverflow, I have cobbled together an IBAction that I'm using in a basic iPhone calculator app that I'm building. The IBAction works with the numeric keypad to allow entry of decimal numbers without having to enter a decimal point.
I am trying very hard to adhere to the "use NSDecimal when dealing with currency" adage although I am finding it difficult to do so like so many others who have posted questions. I am making steady progress, but have hit a wall that I'm sure will look trivial after I get my head around NSDecimal and Format Specifications.
Here is the IBAction I'm using (it is triggered by Editing Changed UITextField Event):
// called when user touches a key or button
- (IBAction)processKeystrokes:(id)sender
{
static BOOL toggle = YES; // was this method triggered by the user?
// the user touched the keypad
if (toggle)
{
toggle = NO;
// retrieve the strings in input fields
NSString *currencyField1Text = currencyField1.text;
NSString *currencyField2Text = currencyField2.text;
if (sender == currencyField1) {
currencyField1Text = [currencyField1Text stringByReplacingOccurrencesOfString:#"." withString:#""];
float currency = [currencyFieldText floatValue]/100;
currencyField1.text = [#"" stringByAppendingFormat:#"%0.2f", currency];
}
else if (sender == currencyField2) {
currencyField2Text = [currencyField2Text stringByReplacingOccurrencesOfString:#"." withString:#""];
NSDecimalNumber *currency2 = [[NSDecimalNumber decimalNumberWithString:currencyField2Text] decimalNumberByDividingBy:[NSDecimalNumber decimalNumberWithString:#"100"]];
currencyField2.text = [#"" stringByAppendingFormat:#"%#", currency2];
}
else {
NSLog(#"Some unexpected input");
}
}
else
{
toggle = YES;
}
} // end method calculateResults
The currencyField1 code segment uses floats, the currencyField2 segment uses NSDecimal.
The currencyField1 segment works as desired: displays all numbers with two digits after the decimal point (even when the delete key is used to delete all entered digits); however it suffers from and illustrates perfectly the problem with using floats when dealing with large currency values: rounding errors show up when entered numbers exceed 8 digits.
The currencyField2 segment avoids rounding error problem by using NSDecimal instead of float; however it does not always display numbers with two digits after the decimal point -- this is shown when the delete key is used to delete all entered digits. I believe the problem is due to this line of code:
currencyField2.text = [#"" stringByAppendingFormat:#"%#", currency2];
This is the corollary to the following line that produces the desired format for floats:
currencyField1.text = [#"" stringByAppendingFormat:#"%0.2f", currency];
So, I think I need the equivalent of #"%0.2f" for formatting the display of a "0" value NSDecimalNumber. I have been at this for so many hours that I'm embarrassed, but I just can't figure it out.
Any help or pointers are appreciated.
EDIT: I incorporated the NSNumberFormatter object (similar to what Brad describes in his comment) which seems to have solved the problem. However, I would like some feedback on refactoring the code now that I have it working. Here's the revised code:
// called when user touches a key or button
- (IBAction)processKeystrokes:(id)sender
{
static BOOL toggle = YES; // was this method triggered by the user?
// the user touched the keypad
if (toggle)
{
toggle = NO;
// retrieve the strings in input fields
NSString *currencyField1Text = currencyField1.text;
NSString *currencyField2Text = currencyField2.text;
// new code elements
NSNumberFormatter * nf = [[[NSNumberFormatter alloc] init]autorelease];
[nf setNumberStyle:NSNumberFormatterCurrencyStyle];
[nf setCurrencySymbol:#""];
[nf setCurrencyGroupingSeparator:#""];
if (sender == currencyField1) {
currencyField1Text = [currencyField1Text stringByReplacingOccurrencesOfString:#"." withString:#""];
NSDecimalNumber *currency = [[NSDecimalNumber decimalNumberWithString:currencyField1Text] decimalNumberByDividingBy:[NSDecimalNumber decimalNumberWithString:#"100"]];
currencyField1.text = [nf stringFromNumber:currency];
}
else if (sender == currencyField2) {
currencyField2Text = [currencyField2Text stringByReplacingOccurrencesOfString:#"." withString:#""];
NSDecimalNumber *currency2 = [[NSDecimalNumber decimalNumberWithString:currencyField2Text] decimalNumberByDividingBy:[NSDecimalNumber decimalNumberWithString:#"100"]];
currencyField2.text = [nf stringFromNumber:currency2];
}
else {
NSLog(#"Some unexpected input");
}
}
else
{
toggle = YES;
}
} // end method calculateResults
It addresses my initial problem, but I would appreciate any advice on how to improve it. Thanks.
If you want to guarantee 2 digits after the decimal point for your text value, you could use an NSNumberFormatter like in the following code (drawn from the answer here):
NSNumberFormatter *decimalNumberFormatter = [[NSNumberFormatter alloc] init];
[decimalNumberFormatter setMinimumFractionDigits:2];
[decimalNumberFormatter setMaximumFractionDigits:2];
currencyField2.text = [decimalNumberFormatter stringFromNumber:currency2];
[decimalNumberFormatter release];
I believe this should preserve the precision of the NSDecimalNumber. Personally, I prefer to use the NSDecimal C struct for performance reasons, but that's a little harder to get values into and out of.
Can't you use the -setFormat: method (of NSNumberFormatter) instead for the NSNumberFormatter? Seems one should be able to configure it for your purposes and you wouldn't have to deal with weird "hacks" on a currency formatted string.
For more info see:
Apple's docs on -setFormat
Accepted format strings