-[__NSArrayI floatValue]: unrecognized selector sent to instance - objective-c

_data = [NSMutableArray new];
NSNumber *value1 = [NSNumber numberWithFloat: 5.0f];
[_data setValue:value1 forKey:#"foothold"];
NSNumber *value2 = [_data valueForKey:#"foothold"];
NSLog(#"a foothold %f ",[value2 floatValue]);//error here
It's strange, but I don't see my error...

You have a few problems.
_data is mistakenly an NSMutableArray instead of an NSMutableDictionary.
Don't use setValue:forKey: and valueForKey: unless you mean to do KVC.
Use modern syntax (it's easier and it avoid issue #2).
Updated code:
_data = [NSMutableDictionary dictionary];
NSNumber *value1 = #5.0;
_data[#"foothold"] = value1;
NSNumber *value2 = _data[#"foothold"];
NSLog(#"a foothold %f ",[value2 floatValue]);

When you get an "unrecognized selector sent to instance" error, you'll be given the name of the method you're attempting to call (in this case floatValue) as well as the type of object you're calling it on (NSArray here).
So in this case, despite value2 being declared as an NSNumber, the value returned from [_data valueForKey:#"foothold"]; is an NSArray, which does not respond to the floatValue selector.
I'm surprised you weren't given other warnings. Given the syntax, it looks like you should be using an NSMutableDictionary rather than an array. In which case, try this:
NSMutableDictionary *_data = [NSMutableDictionary dictionary];
NSNumber *value1 = #5.0f;
_data[#"foothold"] = value1;
NSNumber *value2 = _data[#"foothold"];
NSLog(#"a foothold %f", [value2 floatValue]);

Related

NSDictionary returning same value for different keys

I have some Obj-C code that uses NSNumbers as keys in a dictionary. I'd found a bug that I tracked down to some very strange behavior where if the dictionary is accessed using NSDecimalNumber (a subclass of NSNumber) it would always return the same element. Here's a small program that exhibits the behavior and the NSLogs output the problem:
#define LONGNUMBER1 5846235266280328403
#define LONGNUMBER2 5846235266280328404
- (void) wtfD00d {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSNumber *key1 = [NSNumber numberWithLongLong:LONGNUMBER1];
NSNumber *key2 = [NSNumber numberWithLongLong:LONGNUMBER2];
dict[key1] = #"ONE";
dict[key2] = #"TWO";
NSNumber *decimalKey1 = [NSDecimalNumber numberWithLongLong:LONGNUMBER1];
NSNumber *decimalKey2 = [NSDecimalNumber numberWithLongLong:LONGNUMBER2];
NSString *value1 = dict[decimalKey1];
NSString *value2 = dict[decimalKey2];
NSLog(#"Number of entries in dictionary = %lu", (unsigned long)dict.count); // 2
NSLog(#"%#", dict); // 5846235266280328403 = ONE
// 5846235266280328404 = TWO
NSLog(#"Value1 = %#, Value 2 = %#", value1, value2); // Value1 = ONE, Value 2 = ONE
NSLog(#"key2 = decimalKey2: %#", [key2 isEqual:decimalKey2] ? #"True" : #"False"); // key2 isEqual decimalKey2: True
NSLog(#"decimalKey1 = decimalKey2: %#", [decimalKey1 isEqual:decimalKey2] ? #"True" : #"False"); // decimalKey1 isEqual decimalKey2: False
}
Notice that the 3rd log line shows that value1 and value2 are the same. WHY does this happen?
This came up because we have a few fields in CoreData with type Decimal and they come out of CoreData as NSNumber. We found that we had to play games to get around this strange behavior and I don't understand why it happens in the 1st place. I'd love any insight anyone can offer as to why the failed lookups.
I think that when NSNumber and NSDecimalNumber are compared with each other, they are using doubleValue for comparison.
You can report bug in apple for that
I can suggest only to avoid NSNumber<->NSDecimalNumber comparison and use here:
NSString *value1 = dict[#(decimalKey1.longLongValue)];
NSString *value2 = dict[#(decimalKey2.longLongValue)];

Why is this string immutable?

Why does the code give the error - Attempt to mutate immutable object with appendFormat: ?
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
for (NSTextTestingResult *match in matches) {
<omitted>
NSMutableString *value;
value = (NSMutableString *)[response stringWithRange:range];
if ([dict objectForKey:#"traveler"])
[dict objectForKey:#"traveler"] appendFormat:#"%#", value]; // Errors here
[dict setObject:value forKey:key];
}
Value is being created as a _NSCFString.
Because [response stringWithRange:range] returns an immutable NSString *, and casting doesn't make it become mutable.
You want value = [[response stringWithRange:range] mutableCopy];.
Note that if you're not using ARC, you need to remember to release the mutableCopy. Although the return value of [response stringWithRange:range] is autoreleased, the mutableCopy is not.
I dont think you can cast a string to mutable like that.
You need to do it like this
ms = [[NSMutableString alloc] init];
[ms setString:immutableString];
Oops wrong again the way the subclass works you should be able to do it like this more simply.
ms = [NSMutableString stringWithString: immutableString];

Does iOS coerce NSStrings into NSNumbers?

Subtitle: why does this code work? It seems to allow comparison of NSNumber with NSString types via some sort of coercion. I'm trying to compare a selection from a UISegmentedControl with a previously stored value.
- (IBAction)minSegmentedControlChanged:(id)sender // MINIMUM value
{
UISegmentedControl *s1 = (UISegmentedControl *)sender;
NSMutableArray *pD = [[GameData gameData].curData valueForKey:#"persData"];
// Must make sure max >= min
NSNumber *currMax = [pD objectAtIndex:1];
NSLog(#"%#", [currMax class]); // __NSCFString ?!
int ss1 = s1.selectedSegmentIndex;
NSNumber *SS1 = [NSNumber numberWithInt:ss1 + 2];
if (SS1 >= currMax) SS1 = currMax;
NSLog(#"%#", SS1); // Answer is correct, appears to be an integer
NSLog(#"%#", [SS1 class]); // __NSCFString ?!
[pD replaceObjectAtIndex:0
withObject:SS1];
[[GameData gameData].curData setObject:pD
forKey:#"persData"];
NSLog(#"%#", [[GameData gameData].curData valueForKey:#"persData"]);
}
I am particularly asking about:
NSNumber *currMax = [pD objectAtIndex:1];
NSLog(#"%#", [currMax class]); // __NSCFString ?!
which seems to return a string for a number. [[GameData gameData].curData valueForKey:#"persData"]; is initialized as follows:
_persData = [[NSMutableArray alloc] initWithObjects:#"2", #"8", #"TWL", #"0", #"0", nil];
which is a string at element 1. So why can I ask it for an NSNumber, which reports that it is actually a __NSCFString on which I can do arithmetic comparisons on? I've only been at objective-c for a few months but this seems strange.
Okay, let's walk through this one step at a time.
First of all, all of the elements in _persData are strings. Period. NSString is a class cluster, so the concrete classes of the various instances you inquire about may look weird, but that's to support toll-free bridging and other magic that's not relevant to this discussion.
NSNumber *currMax = [pD objectAtIndex:1];
This line is incorrect. You might think there's some sort of coercion going on, but actually you're just assigning an NSString * to an NSNumber *. Which is wrong, and will explode in your face at the earliest convenience. It so happens that objectAtIndex: returns an id, which is stripped of type information, so the compiler is trusting you to store it in the right kind of pointer, but that's not enforced until you try to send a message to it.
if (SS1 >= currMax) SS1 = currMax;
This is an extremely wily comparison. SS1 is most certainly an NSNumber, but currMax is an NSString. But we're not comparing the values of those objects. To do that, we'd use the compare: method. Instead, we're comparing them as pointers, looking only at their addresses in memory. By some accident of implementation, SS1 seems to always reside at a higher address than currMax.
If all of the foregoing is true, then SS1 is always of type NSString after the above line is executed, which explains why this line:
NSLog(#"%#", [SS1 class]);
Always indicates that SS1 is a string.
You actually initialize the NSMutableArray with NSString, not number.
When you pull object out of NSMutableArray with objectAtIndex, the return type of the function is id, which it will blindly cast to NSNumber, while the actual object is NSString.
It seems that the SS1 >= currMax statement is comparing the addresses of the objects instead of their values.
You can test it out with this snippet of code:
NSNumber *a = [[NSNumber alloc] initWithUnsignedInt: 34];
NSNumber *b = [[NSNumber alloc] initWithUnsignedInt: 3];
NSNumber *c = [[NSNumber alloc] initWithUnsignedInt: 34];
NSNumber *d = [[NSNumber alloc] initWithUnsignedInt: 234];
NSLog(#"%p %p %p %p %d %d", a, b, c, d, a >= b, c >= d);

can't get value from NSDictionary which expected to have NSNumber as output

I have created a NSDictionary like following :
NSArray * alphabets = [NSArray arrayWithObjects:#"a",#"b",#"c",#"d",#"e",#"f",#"g",#"h",#"i",#"j",#"k",#"l",#"m",#"n",#"o",#"p",#"q",#"r",#"s",#"t",#"u",#"v",#"w",#"x",#"y",#"z",nil];
alphaToNum = [NSMutableDictionary dictionary];
numToAlpha = [NSMutableDictionary dictionary];
for(NSString* character in alphabets)
{
[alphaToNum setObject:[NSNumber numberWithInteger:index] forKey:character];
[numToAlpha setObject:character forKey:[NSNumber numberWithInteger:index]];
index++;
}
now I want to access to "numToAlpha" like following :
NSInteger code1;
NSNumber * nn = [numToAlpha objectForKey:code1];
and I'll get error whereas in manual for objectforkey it clearly said (id)objectforkey(id) which means anything!
NSInteger is not an object. id refers to any object type. You need to use [NSNumber numberWithInteger:code1] as you are already doing in your code sample.

assigning NSNumber object to NSString object won't cause any error or warning

I've put NSNumber object into NSDictionary object, and then pop and assign it to a variable declared as NSString instance.
NSString *test;
NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:1000] forKey:#"key"];
test = [dict valueForKey:#"key"];
NSLog(#"%#, type of %#", test, NSStringFromClass([test class]));
return 0;
After running the above code, I've found that the type of test, declared as (NSString *) is __NSCFNumber. Why did it happen and why compiler did give no warning or errors?
Do I have to NSString constructor, such as NSStringWithFormat..., in order to keep test as NSString's instance?
The reason this did not fail or warn you is because valueForKey: returns an object of type id which you can assign to any Objective-C type. You need to be aware of what type of value you are getting back from your collections to safely use them. In this case you know it contains an NSNumber so you should expect an NSNumber and if you need a string you will need to do the proper conversions.
NSNumber *test = [dict valueForKey:#"key"];
NSLog(#"%#, type of %#", test, NSStringFromClass([test class]));
//If you need a string
NSString *testStr = [test stringValue];