views:

655

answers:

2

Hello,

in my current project I'm facing the following problem:

The app needs to exchange data with my server, which are stored inside a NSMutableArray on the iPhone. The array holds NSString, NSData and CGPoint values. Now, I thought the easiest way to achieve this, was to convert the array into a properly formatted string, send it to my server and store it inside some mySQL database. At this point I'd like to request the string, which represents contents of my array, from my server and then actually convert it back into a NSMutablArray.

So far, I tried something like this:

NSString *myArrayString = [myArray description];

Now I send this string to my server and store it inside my mySQL database. That part works really well.

However, when I receive the string from my server, I have trouble converting it back into a NSMutableArray.

Is there a method, which can easily convert array description back into an array? Unfortunately I couldn't find anything on that so far. Maybe my way of "serializing" the array is wrong right from the beginning and there is a smarter way to do this.

Any help appreciated. Thanks in advance.

+2  A: 

Don't use description for that, since that's for getting it into a human-readable "pretty" format. What you want is a data dump of the object.

What you might want to do instead is leverage the fact that NSArray does NSCoding to get an array of raw bytes from it -- NSData. (This is the serialization you mentioned.) You could then transfer the raw bytes to your server, use zip compression, or encode the bytes in base-64 to send in a query string over HTTP. To recover the array, you'd just reverse the process.

There are plenty of existing questions and answers on Stack Overflow to help you out with that. Here are a couple:

http://stackoverflow.com/questions/1286212/how-to-convert-nsarray-to-nsdata

http://stackoverflow.com/questions/2197362/converting-nsdata-to-base64

Shaggy Frog
Thanks. So, If I understand it correctly I should1) Convert the array into raw bytes = NSData object ?2) base64 encode (1) and send it to my server as string3) Convert base64 string from my server back into raw bytes4) Turn (3) into a new arrayIs that about right?
Friendlydeveloper
Yup, you've got it. But like I said, depending on the underlying transport, you may be able to skip the base-64 encoding step. But my guess is you're communiting with your MySQL server via HTTP, correct?
Shaggy Frog
Yes, using ASIHTTPRequest actually and submitting via POST.
Friendlydeveloper
That's probably the way to go, then. Depending on the length of the base-64 encoded string... you probably aren't going to be able to send a few million encoded characters or anything :)
Shaggy Frog
Nah, it's nowhere near that :)
Friendlydeveloper
A: 

Alright, I think I got a bit closer. However, I'm not quite there yet, since I ran into some "EXC_BAD_ACCESS" when converting data back to a new array.

Let me share some code with you. The following is supposed to take care of converting the array to NSData, converting NSData to base64 and vice versa.

@interface NSArray (dataConversion)
    + (NSArray*) arrayWithData:(NSData*) data;
    - (NSData*) convertToData;
@end

@implementation NSArray (dataConversion)


- (NSData*) convertToData {
    unsigned n= [self count]; 
NSMutableData* data = [NSMutableData dataWithLength: sizeof(unsigned)+
                       sizeof(id) *n];
unsigned* p = [data mutableBytes];
*p++= n;
[self getObjects:(void*)p];
return data;
}

+ (NSArray*) arrayWithData:(NSData*) data {
unsigned* p = (unsigned*)[data bytes];
unsigned n = *p++;
return [NSArray arrayWithObjects:(id*)p count:n];
}

@end

@interface NSData (MBBase64)

+ (id)dataWithBase64EncodedString:(NSString *)string;     //  Padding '=' characters are     optional. Whitespace is ignored.
- (NSString *)base64Encoding;
@end


static const char encodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";


@implementation NSData (MBBase64)

+ (id)dataWithBase64EncodedString:(NSString *)string;
{
if (string == nil)
    [NSException raise:NSInvalidArgumentException format:nil];
if ([string length] == 0)
    return [NSData data];

static char *decodingTable = NULL;
if (decodingTable == NULL)
{
    decodingTable = malloc(256);
    if (decodingTable == NULL)
        return nil;
    memset(decodingTable, CHAR_MAX, 256);
    NSUInteger i;
    for (i = 0; i < 64; i++)
        decodingTable[(short)encodingTable[i]] = i;
}

const char *characters = [string cStringUsingEncoding:NSASCIIStringEncoding];
if (characters == NULL)     //  Not an ASCII string!
    return nil;
char *bytes = malloc((([string length] + 3) / 4) * 3);
if (bytes == NULL)
    return nil;
NSUInteger length = 0;

NSUInteger i = 0;
while (YES)
{
    char buffer[4];
    short bufferLength;
    for (bufferLength = 0; bufferLength < 4; i++)
    {
        if (characters[i] == '\0')
            break;
        if (isspace(characters[i]) || characters[i] == '=')
            continue;
        buffer[bufferLength] = decodingTable[(short)characters[i]];
        if (buffer[bufferLength++] == CHAR_MAX)      //  Illegal character!
        {
            free(bytes);
            return nil;
        }
    }

    if (bufferLength == 0)
        break;
    if (bufferLength == 1)      //  At least two characters are needed to produce one byte!
    {
        free(bytes);
        return nil;
    }

    //  Decode the characters in the buffer to bytes.
    bytes[length++] = (buffer[0] << 2) | (buffer[1] >> 4);
    if (bufferLength > 2)
        bytes[length++] = (buffer[1] << 4) | (buffer[2] >> 2);
    if (bufferLength > 3)
        bytes[length++] = (buffer[2] << 6) | buffer[3];
}

realloc(bytes, length);
return [NSData dataWithBytesNoCopy:bytes length:length];
}

- (NSString *)base64Encoding;
{
if ([self length] == 0)
    return @"";

char *characters = malloc((([self length] + 2) / 3) * 4);
if (characters == NULL)
    return nil;
NSUInteger length = 0;

NSUInteger i = 0;
while (i < [self length])
{
    char buffer[3] = {0,0,0};
    short bufferLength = 0;
    while (bufferLength < 3 && i < [self length])
        buffer[bufferLength++] = ((char *)[self bytes])[i++];

    //  Encode the bytes in the buffer to four characters, including padding "=" characters if necessary.
    characters[length++] = encodingTable[(buffer[0] & 0xFC) >> 2];
    characters[length++] = encodingTable[((buffer[0] & 0x03) << 4) | ((buffer[1] & 0xF0) >> 4)];
    if (bufferLength > 1)
        characters[length++] = encodingTable[((buffer[1] & 0x0F) << 2) | ((buffer[2] & 0xC0) >> 6)];
    else characters[length++] = '=';
    if (bufferLength > 2)
        characters[length++] = encodingTable[buffer[2] & 0x3F];
    else characters[length++] = '=';    
}

return [[[NSString alloc] initWithBytesNoCopy:characters length:length encoding:NSASCIIStringEncoding freeWhenDone:YES] autorelease];
}

@end

Now, I use it like that:

NSData *messageData = [[NSArray arrayWithArray:myNSMutableArray] convertToData];
NSString *messageData = [messageData base64Encoding];

messageData is the string I now send to my server. This works fine.

Now the other way round:

NSData *arrayData = [NSData dataWithBase64EncodedString:serverResponseString]; //serverResponseString is the string returned from my server upon request

NSMutableArray *newArray = [[NSMutableArray alloc] initWithArray:[NSArray arrayWithData:arrayData]]; //here it crashes and points to: "return [NSArray arrayWithObjects:(id*)p count:n];"

I'm either missing something here or completely off track. Any help appreciated. Thanks.

Friendlydeveloper
Ok, I digged down a little deeper and created a new project just to test serializing and de-serializing my array. I filled the array with objects similar to those I have in my original project. Guess what? It worked. So what does that tell me? Maybe there is a problem with the string, that gets returned from my server? Not sure yet. I'll investigate and report back.
Friendlydeveloper
Alright, that was fast. I think I found the problem. Let me explain: When I serialize my array, I get a string similar to this one (without quotes): "GwAAAPAWEgCQEhwAwBAcANAPHADwFhIAAFcZABBcGQCgUxsAQBAcALAhHADANRwA4CscADDcEACwERwAYDkcAPAUEgAQmBIAQFUbAOBaGwDQXRsAcB0cADBYGwAgUBsAkFUbAPBRGwDw/xsAMFkcAA=="Now, if I tell my app to deserialize this string right after serializing it, it works. However, when I want to deserialize the very same string like NSString *str = @"stringFromAboveHere"; it crashes. Know what I mean?
Friendlydeveloper
Now this is weird. After some more testing I found something interesting. As already mentioned above, I created a new test app. I filled this app with objects. Those objects are the same every time and they are located at the same position every time. However, when I restart the app and serialize the array, the serialized string is completely different each time! Why is that? I'm sure, solving this is the key to getting rid of my problem.
Friendlydeveloper