Using NSData for your network messages

One of the hurdles you have to overcome when building an app like Downside is how to handle network messages. Apple provides several ways of adding networking to your apps including GameKit and the new Multipeer Connectivity framework in iOS 7. Each of them require sending and receiving NSData blobs. Unfortunately Apple has not given us a nice and neat way of packing our data into these blobs.

NSData (and its mutable counterpart) represents a continuous block of memory. Inside this block of memory we can put the values of integers, strings, and other variables. The trick is knowing how big the variable we want to store in our NSData blob is and how to store it.

With an integer, short, or byte, storing the value is pretty easy. Lets look at an example for an integer.

int value = 42;
[data appendBytes:&value length:sizeof(value)];

To store something like a string, we either have to reserve a chunk of memory and constrain the string to that size or we store the size of the string before the string’s data.

To store a variable length string, we would do something like this:

-----------------
|12|Hello world!|
-----------------

char *characters = (char *)malloc(value.count);
[value getCString:characters maxLength:sizeof(characters) encoding:NSUTF8StringEncoding];
[data appendBytes:&characters length:sizeof(characters)];   

Lets say we want to send the following data over our network connection:

@property (nonatomic, readonly) NSString *identifier;
@property (nonatomic) NSString *username;
@property (nonatomic) NSUInteger state;

Knowing that we are limiting the identifier to 8 characters, we can then build a method to build our NSData blob.

- (NSData *)data {
  NSMutableData *data = [NSMutableData data];
  char *characters = (char *)malloc(255);

  [_identifier getCString:characters maxLength:8 encoding:NSUTF8StringEncoding];
  [data appendBytes:&characters length:8];

  [_username getCString:characters maxLength:255 encoding:NSUTF8StringEncoding];
  // write the size of the username
  unsigned int length = htonl(strlen(characters));
  [data appendBytes:&length length:sizeof(unsigned int)];

  // write the username
  [data appendBytes:&characters length:length]; 

  unsigned int state = htonl(_state);
  [data appendBytes:&state length:sizeof(state)];
  return data;
}

The other end of the equation is how to get the data back out of the packet once it has traveled over the network. To do so, we make heavy use of the getBytes:range: method of NSData. This time we use the size of each piece of data we want to retrieve to increment an offset to the next piece of data.

- (void)packetFromData:(NSData *)data {
  void *buffer = malloc(255);
  unsigned int offset = 0;
  [data getBytes:buffer length:8];
  _identifier = [[NSString alloc] initWithBytes:buffer length:8 encoding:NSUTF8StringEncoding]; 
  offset += 8;
  [data getBytes:buffer range:NSMakeRange(offset, sizeof(unsigned int))];
  unsigned int size = ntohl((unsigned int) buffer);
  offset += sizeof(unsigned int);
  [data getBytes:buffer range:NSMakeRange(offset, size)];
  _username = [[NSString alloc] initWithBytes:buffer length:size encoding:NSUTF8StringEncoding];
  offset += size;
  [data getBytes:buffer range:NSMakeRange(offset, sizeof(unsigned int))];
  _state = ntohl((unsigned int)buffer);
}

With these examples in mind, you should be able to create your own Packet class that can send your app’s data across a network connection.

tim@collectiveidea.com

Comments