I'm trying to get a double value from a dictionary. How can I accomplish this in objective-c?
Dave's response to your previous question holds true for this, as well. To store a double value in an NSDictionary, you will need to box it in an NSNumber.
To set a double value in the dictionary, you'd use code like the following:
[someDict setObject:[NSNumber numberWithDouble:yourDouble] forKey:#"yourDouble"];
and read it back using the following:
double isTrue = [[someDict objectForKey:#"yourDouble"] doubleValue];
Brad Larson's response is exactly right. To elaborate on this a little more, you have to explicitly "wrap up" non-object number types (e.g., int, unsigned int, double, float, BOOL, etc.) into NSNumber when working with anything that expects an object.
On the other hand, however, some mechanisms in Objective-C, like Key-Value Coding (KVC), will automatically do this wrapping for you.
For example, if you have a #property of type int called intProperty, and you call NSObject (NSKeyValueCoding)'s valueForKey: method like [ someObject valueForKey:#"intProperty" ], the return result will be an NSNumber *, NOT an int.
Frankly, I don't care for having to switch between dealing with object and non-object types (especially structs and enums!) in Objective-C. I'd rather everything be treated as an object, but maybe that's just me. :)
Related
When I attempt to create an NSNumber using the numberWithLongLong with a number greater than -2 and less than 13 it returns a number that is casted as an (int).
I see this if I look at the Xcode debugger after stepping over my line.
NSNumber* numberA = [NSNumber numberWithLongLong:-2]; //Debugger shows as (long)-2
NSNumber* numberB = [NSNumber numberWithLongLong:-1]; //Debugger shows as (int)-1
NSNumber* numberC = [NSNumber numberWithLongLong:12]; //Debugger shows as (int)12
NSNumber* numberD = [NSNumber numberWithLongLong:13]; //Debugger shows as (long)13
To put my problem in context, I am using a long long value for an epoch date that I will end up serializing using BSON and sending across the wire to a webservice. The webservice requires the date to be a java Long.
Thanks in advance
You have discovered that NSNumber (actually, its CFNumber counterpart) has a cache for integers between -1 and 12 inclusive. Take a look at the CFNumberCreate function in CFNumber.c to see how it works.
It looks like you can force it not to use the cache by passing your own allocator to CFNumberCreate. You'll need to look at the CFAllocator documentation.
But note that the CFNumberCreate manual says this:
The theType parameter is not necessarily preserved when creating a new CFNumber object.
So even if you bypass the cache, you might not get back an object whose objCType is q (which means long long). It looks like the current implementation will return q but that could change in a future version.
You are allowed to write your own NSNumber subclass if you need to guarantee that objCType returns q. Read “Subclassing Notes” in the NSNumber Class Reference.
You can use your webservice without concern.
NSNumber wraps a numeric value (of primitive type) as an object. How NSNumber stores that value is not really your concern (but there is a method to find it out), it is an opaque type. However NSNumber does maintain an internal record of the type used to create it so its compare: method can follow C rules for comparison between values of different types precisely.
For integral types the integral value you get back will be exactly the same, in the mathematical sense, as the one you created the NSNumber with. You can create an NSNumber with a short and read its value back as a long long, and the mathematical value will be the same even though the representation is different.
So you can store your integral date value as an NSNumber and when you read it back as a long long you will get the right value. No need to be concerned how NSNumber represents it internally, and indeed that could potentially change in the future.
(At least one implementation of NSNumber can store values as 128-bit integers, which helps ensure correct semantics for signed and unsigned integers. Also I stressed integral types as with the vagaries of real numbers talking about mathematical exactness is somewhat moot.)
Wait. I think I know what your asking. Try it this way:
NSNumber* numberA = [NSNumber numberWithLongLong:-2LL];
NSNumber* numberB = [NSNumber numberWithLongLong:-1LL];
NSNumber* numberC = [NSNumber numberWithLongLong:12LL];
NSNumber* numberD = [NSNumber numberWithLongLong:13LL];
BTW: it won't matter what the type of the constant is, it will be coerced into a long long when passed to [NSNumber numberWithLongLong:]
UPDATE
Based on #robmayoff's answer, I don't think NSNumber is reliable for your. How are you packing your BSON? is there a way to use NSValue instead of NSNumber?
What is the highest int that NSNumber allows? I've seen the answer elsewhere on these forums hence why I'm deeply confused here.
int miles = 35000;
vehicle.mileage = [NSNumber numberWithInt:miles];
NSLog(#"int value = %d", miles);
NSLog(#"mileage = %#", vehicle.mileage);
The output is:
int value = 35000
mileage = -30536
I must be missing some terrible easy here, but can someone explain to me why this is not working correctly?
UPDATE:
After looking further, vehicle.mileage is getting set correctly to 35000 but when I display this via NSLog(#"%#", vehicle.mileage) it is outputting it incorrectly. I have yet to find the "magic" value when this stops working because as of now, it works for values up to ~30,000.
NSNumber is just a wrapper so it goes in overflow when the wrapped primitive type goes in overflow.
So if you use numberWithInt the maximum number allowed is INT_MAX (defined in limits.h), if you use a numberWithFloat the maximum number allowed is FLOAT_MAX, and so on.
So in this case you aren't going in overflow, I doubt that INT_MAX would be so low.
Overview
NSNumber is a subclass of NSValue that offers a value as any C scalar
(numeric) type. It defines a set of methods specifically for setting
and accessing the value as a signed or unsigned char, short int, int,
long int, long long int, float, or double or as a BOOL. (Note that
number objects do not necessarily preserve the type they are created
with.) It also defines a compare: method to determine the ordering of
two NSNumber objects.
So NSNumber is as big as what it wraps. For your unexpected result you can check comment bellow your qestion from #sjs.
+numberWithInt: interprets the value as signed int. Mileage would never be negative, so I suggest using [NSNumber numberWithUnsignedInt:]
The limit NSNumber integer can have is known as INT_MAXbut 35, 000 is nowhere close to that. The problem must be with vehicle object or the mileage property in the vehicle, either of them may be nil
So, go ahead and log with this conditional statement:
if (!vehicle) {
NSLog(#"Vehicle is nil");
}
else if (!vehicle.mileage) {
NSLog(#"Vehicle's mileage is nil");
}
Tell me your result
Not sure why Objective-C decided to use NSNumber instead of float, double, etc. How is this type represented on disk?
NSNumber is toll-free bridged with CFNumber. In recent implementations of Core Foundation, CFNumber is a tagged pointer. This lets it be treated as an object, but without all the overhead of an object. Instead, the value is encoded in the object pointer (and isn't actually a pointer).
See Tagged pointers and fast-pathed CFNumber integers in Lion.
NSNumber is a descendant of NSObject, so it can go wherever an id can go: NSarray, NSDictionary, and so on. Primitives such as int and double cannot go in these classes, because they do not inherit from NSObject, and hence cannot participate in collections etc.
If I were to guess on the internals of NSNumber. I'd say it's a union and a type selector field. However, the beauty of encapsulation lets me successfully program to NSNumber without knowing a first thing about its representation (and not missing that knowledge).
One thing to keep in mind is that Objective-C is a super-set of C, so they didn't decide to use NSNumber instead of the primitive types (float, double, etc.) but in addition to them. If you don't need the functionality of NSNumber, then just use the primitive types and save the overhead of creating/destroying the objects. Many functions in iOS (notably the array type functions) only work with objects (descendants of NSObject). Therefore, if you want to pass some type of number to one of these functions, you need an object representation of it. This is where NSNumber comes in.
To quote the documentation on NSNumber:
NSNumber is a subclass of NSValue that offers a value as any C scalar
(numeric) type. It defines a set of methods specifically for setting
and accessing the value as a signed or unsigned char, short int, int,
long int, long long int, float, or double or as a BOOL. (Note that
number objects do not necessarily preserve the type they are created
with.) It also defines a compare: method to determine the ordering of
two NSNumber objects.
Note that internally the actual value is stored either as an integer or as a floating point number (within either a tagged pointer as Jay describes or a union in an object), depending on what value you are storing. This is important to know as if you try to store a number like "32.1" it will store it as a floating point number and when you retrieve it you will most likely get something like "32.09999999999999".
As far as storing it to disk, if you need to do this then you typically store it with encodeWithCoder and retrieve it with initWithEncoder which converts it to a format intended to be saved to disk and later read back in.
I have a function that sets an entity within a Core Data store. I used to have all values it would be storing as type double, however now I must make it accommodate NSStrings as well. Consequently, I changed the type of the parameter the function takes in, to an id type. However, now I get the error:
error: incompatible type for argument 1 of 'numberWithDouble:'
...at the following lines:
//...
[dfm setTimeStamp:[NSNumber numberWithDouble:value]];
//...
[[fetchedObjects objectAtIndex:0] setValue:[NSNumber numberWithDouble:value] forKey:#"timeStamp"];
//...
Apparently it doesn't like the [NSNumber numberWithDouble:value] segment of each line. I was contemplating making a container class that holds an NSNumber type (doesn't Apple already have a class like this?) to get around this problem, but I thought that there has to be an easier way I am not thinking of (besides duplicating the function and changing the type of the value parameter). Any ideas? Thanks in advance!
EDIT:
Here is the function declaration:
-(void)setItemInDFMWhilePreservingEntityUniquenessForItem:(attribute)attr withValue:(id)value
attribute is merely an enum which specifies which entity to store within. The problem is that the compiler is giving me problems with value being of type id, theoretically I can pass in anything I want, and I believe the way I have it I am implying that I will be passing it as an NSNumber, but the compiler doesn't like that as that is not actually a class instance I suppose?
The problem is that the compiler is
giving me problems with value being of
type id, theoretically I can pass in
anything I want, and I believe the way
I have it I am implying that I will be
passing it as an NSNumber, but the
compiler doesn't like that as that is
not actually a class instance I
suppose?
By declaring value as id, you can pass any object you want. But why do you "suppose" that NSNumber isn't an object, when it's clearly documented as being an object? The warning isn't about passing an NSNumber instance when you've declared value as an id - that's perfectly valid, because id means "any object," and an NSNumber instance is an object. The warning comes from calling +numberWithDouble:, a method that takes a double for its first argument, and passing it value, which is declared as id - i.e. an object. You can't pass an object to a method that expects a double.
Your proposed solution, typecasting value with (NSInteger)value will silence the warning, but it won't fix the problem. The typecast simply converts the memory address the object pointer targets to an integer value. If (as your edit suggests) value is already an NSNumber object, what do you hope to gain by creating another one, or by typecasting its memory address to an integer? Just do:
[dfm setTimeStamp:value];
The problem lies with the value variable. It should be declared as a double (primitive) for this call to succeed.
edit: after rereading your question, do a check in the function on the type of value, if it is an NSString (use [value isKindOfClass:[NSString class]]) store it as such, if its not then its a double (if thats the only two types you are passing) and store it as such.
Can't you just pass the NSNumber instead of double?
Just realized that the call I was making (numberWithDouble:) was having the compiler check for a primitive, i.e. double. Changing it to the following worked like a charm:
[dfm setTimeStamp:[NSNumber numberWithInteger:(NSInteger)value]];
Thanks to those that responded!
I using something like this : [tmpArray insertObject:something[i] atIndex:i];
But I got a MSG : passing argument 1 of "insertObject:atIndex:" makes pointer from integer without a cast.
What should I do to fix it?
I guess your something[] array is a C array of int, if this is the case you cannot pass its value at that position into an NSArray since it contains objects (and objects in obj-C are referenced by pointers).
What you could do is wrap the int into a NSNumber this way:
[tmpArray insertObject:[NSNumber numberWithInt:something[i]] atIndex:i];
You can't magically make random pointer into an object, you need to understand what the type of the pointer is and do something semantically reasonable with that type. Also, that is not what the error you are seeing is about. The error is because the type expected is a pointer (and an object in ObjC is also a pointer type), and the type info of the thing you are passing is a scalar.
Taking a wild guess, something is probably an array of ints, if so what you want to do is wrap is the integer in an NSNumber in order to store it in a collection:
//assuming this:
int something[100];
//then your line should be this:
[tmpArray insertObject:[NSNumber numberWithInt:something[i]] atIndex:i];
Obviously you will need to unbox the value anywhere you access it. If it is not an array of integers then you will need to do something else (though probably similiar), but without more info I can't tell you exactly what.