views:

629

answers:

2

I'm working with Objective-C and I need to add int's from a NSArray to a NSMutableData (I'm preparing a to send the data over a connection). If I wrap the int's with NSNumber and then add them to NSMutableData, how would I find out how many bytes are in the NSNumber int? Would it be possible to use sizeof() since according to the apple documentation, "NSNumber is a subclass of NSValue that offers a value as any C scalar (numeric) type."?

Example:

NSNumber *numero = [[NSNumber alloc] initWithInt:5];

NSMutableData *data = [[NSMutableData alloc] initWithCapacity:0];

[data appendBytes:numero length:sizeof(numero)];
+6  A: 

numero is not a numeric value, it is a pointer to a an object represting a numeric value. What you are trying to do won't work, the size will always be equal to a pointer (4 for 32 bit platforms and 8 for 64 bit), and you will append some garbage pointer value to your data as opposed to the number.

Even if you were to try to dereference it, you cannot directly access the bytes backing an NSNumber and expect it to work. What is going on is an internal implementation detail, and may vary from release to release, or even between different configurations of the same release (32 bit vs 64 bit, iPhone vs Mac OS X, arm vs i386 vs PPC). Just packing up the bytes and sending them over the wire may result in something that does not deserialize properly on the other side, even if you managed to get to the actual data.

You really need to come up with an encoding of an integer you can put into your data and then pack and unpack the NSNumbers into that. Something like:

NSNumber *myNumber = ... //(get a value somehow)
int32_t myInteger = [myNumber integerValue]; //Get the integerValue out of the number
int32_t networkInteger = htonl(myInteger); //Convert the integer to network endian
[data appendBytes:&networkInteger sizeof(networkInteger)]; //stuff it into the data

On the receiving side you then grab out the integer and recreate an NSNumber with numberWithInteger: after using ntohl to convert it to native host format.

It may require a bit more work if you are trying to send minimal representations, etc.

The other option is to use an NSCoder subclass and tell the NSNumber to encode itself using your coder, since that will be platform neutral, but it may be overkill for what you are trying to do.

Louis Gerbarg
I'm still getting the error: "warning: passing argument 1 of 'appendBytes:length:' makes pointer from integer without cast"
Josh Bradley
That last line should be:[data appendBytes:
Jens Alfke
That did it...thanks. On a side note, I just wanted to point out that in apple's documentation it says to place "length:" before the sizeof(). It's part of the method name. The full example in their documentation goes something like this;- (void)appendBytes:(const void *)bytes length:(NSUInteger)lengthI didn't want to say anything but when two people didn't see this, I thought I should mention it.
Josh Bradley
Thanks Jens, writing code in a browser textfield is error prone ;-)
Louis Gerbarg
A: 

First, NSNumber *numero is "A pointer to a NSNumber type", and the NSNumber type is an Objective-C object. In general, unless specifically stated somewhere in the documentation, the rule of thumb in object-oriented programming is that "The internal details of how an object chooses to represent its internal state is private to the objects implementation, and should be treated as a black box." Again, unless the documentation says you can do otherwise, you can't assume that NSNumber is using a C primitive type of int to store the int value you gave it.

The following is a rough approximation of what's going on 'behind the scenes' when you appendBytes:numero:

typedef struct {
  Class isa;
  double dbl;
  long long ll;
} NSNumber;

NSNumber *numero = malloc(sizeof(NSNumber));
memset(numero, 0, sizeof(NSNumber));
numero->isa = objc_getClass("NSNumber");

void *bytes = malloc(1024);

memcpy(bytes, numero, sizeof(numero)); // sizeof(numero) == sizeof(void *)

This makes it a bit more clear that what you're appending to the NSMutableData object data is the first four bytes of what ever numero is pointing to (which, for an object in Obj-C is always isa, the objects class). I suspect what you "wanted" to do was copy the pointer to the instantiated object (the value of numero), in which case you should have used &numero. This is a problem if you're using GC as the buffer used by NSMutableData is not scanned (ie, the GC system will no longer "see" the object and reclaim it, which is pretty much a guarantee for a random crash at some later point.)

It's hopefully obvious that even if you put the pointer to the instantiated NSNumber object in to data, that pointer only has meaning in the context of the process that created it. A pointer to that object is even less meaningful if you send that pointer to another computer- the receiving computer has no (practical, trivial) way to read the memory that the pointer points to in the sending computer.

Since you seem to be having problems with this part of the process, let me make a recommendation that will save you countless hours of debugging some extremely difficult implementation bugs you're bound to run in to:

Abandon this entire idea of trying to send raw binary data between machines and just send simple ASCII/UTF-8 formatted information between them.

If you think that this is some how going to be slow, or inefficient, then let me recommend that you bring every thing up using a simplified ASCII/UTF-8 stringified version first. Trust me, debugging raw binary data is no fun, and the ability to just NSLog(@"I got: %@", dataString) is worth its weight in gold when you're debugging your inevitable problems. Then, once everything has gelled, and you're confident that you don't need to make any more changes to what it is you need to exchange, "port" (for lack of a better word) that implementation to a binary only version if, and only if, profiling with Shark.app identifies it as a problem area. As a point of reference, these days I can scp a file between machines and saturate a gigabit link with the transfer. scp probably has to do about five thousand times as much processing per byte to compress and encrypt the data than this simple stringification all while transferring 80MB/sec. Yet on modern hardware this is barely enough to budge the CPU meter running in my menu bar.

johne