Search code examples
iosparsingnsdata

Finding Different Components from NSData: NSData Parsing


I am having NSData in format as "Hello$World$Image"($ is used as Delimeter to differentate different part Data) That i have made by using Following Code

NSData *data=[@"$" dataUsingEncoding:NSUTF8StringEncoding];
NSLog(@"%@",data);
NSData *data2=[@"Hello" dataUsingEncoding:NSUTF8StringEncoding];
NSLog(@"%@",data2);
NSData *data3=[@"World" dataUsingEncoding:NSUTF8StringEncoding];
NSLog(@"%@",data3);
NSData *dataImage=UIImagePNGRepresentation([UIImage imageNamed:@"Hello.png"]);
NSLog(@"%@",dataImage);


NSMutableData *mdata=[NSMutableData dataWithData:data2];
[mdata appendData:data];
[mdata appendData:data3];
[mdata appendData:data];
[mdata appendData:dataImage];
NSLog(@"%@",mdata);

From this, I got mdata variable that is having bytes with format Hello$World$Image,

Now I Want reverse thing of this and want to get Hello as string1, World as String2, and Image to store in some path, But i am not getting How to parse the NSData for "$" and get the different components out. Any help will be appreciated. Thanks in Advance


Solution

  • Given your comment that you're stuck with this $ delimited format, you should refer to the Binary Data Programming Guide which describes how to extract binary data from a NSData. First, to create the $ delimited NSData, you could do:

    NSString *path = [[NSBundle mainBundle] pathForResource:@"Hello" ofType:@"png"];
    NSData *imageData = [NSData dataWithContentsOfFile:path];
    
    NSMutableData *data = [NSMutableData data];
    
    [data appendData:[@"Hello" dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:[@"$" dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:[@"World" dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:[@"$" dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:imageData];
    

    Then, to extract these two strings and the image, you could:

    NSUInteger len = [data length];
    
    NSString *delimiterString = @"$";
    NSData *delimiterData = [delimiterString dataUsingEncoding:NSUTF8StringEncoding];
    
    NSRange firstDelimiterRange = [data rangeOfData:delimiterData
                                            options:0
                                              range:NSMakeRange(0, len)];
    
    NSUInteger firstDelimiterIndex = firstDelimiterRange.location;
    NSAssert(firstDelimiterIndex != NSNotFound, @"First delimiter not found");
    
    NSRange secondDelimiterRange = [data rangeOfData:delimiterData
                                             options:0
                                               range:NSMakeRange(firstDelimiterIndex + 1, len - firstDelimiterIndex - 1)];
    
    NSUInteger secondDelimiterIndex = secondDelimiterRange.location;
    NSAssert(secondDelimiterIndex != NSNotFound, @"Second delimiter not found");
    
    NSData   *subdata1 = [data subdataWithRange:NSMakeRange(0, firstDelimiterIndex)];
    NSString *string1  = [[NSString alloc] initWithData:subdata1 encoding:NSUTF8StringEncoding];
    NSAssert(string1, @"string1 not found");
    
    NSData   *subdata2 = [data subdataWithRange:NSMakeRange(firstDelimiterIndex + 1, secondDelimiterIndex - firstDelimiterIndex - 1)];
    NSString *string2  = [[NSString alloc] initWithData:subdata2 encoding:NSUTF8StringEncoding];
    NSAssert(string2, @"string2 not found");
    
    NSData   *subdata3 = [data subdataWithRange:NSMakeRange(secondDelimiterIndex + 1, len - secondDelimiterIndex - 1)];
    UIImage  *image    = [UIImage imageWithData:subdata3];
    NSAssert(image, @"valid image not found");
    

    My original answer, below, focused on possibly better ways to store two strings and a binary image. Given your subsequent comments, perhaps the ugly code above might work for you, but I'll keep my original answer for future reference.


    A couple of thoughts:

    1. If you were dealing with just strings (that were guaranteed not to have a $ in them), you could construct your NSData like so:

      NSData *data=[@"$" dataUsingEncoding:NSUTF8StringEncoding];
      NSLog(@"%@",data);
      NSData *data2=[@"Hello" dataUsingEncoding:NSUTF8StringEncoding];
      NSLog(@"%@",data2);
      NSData *data3=[@"World" dataUsingEncoding:NSUTF8StringEncoding];
      NSLog(@"%@",data3);
      
      NSMutableData *mdata=[NSMutableData dataWithData:data2];
      [mdata appendData:data];
      [mdata appendData:data3];
      NSLog(@"%@",mdata);
      

      And then you could extract the strings like so:

      NSString *fullString = [[NSString alloc] initWithData:mdata encoding:NSUTF8StringEncoding];
      NSArray  *components = [fullString componentsSeparatedByString:@"$"];
      NSLog(@"components = %@", components);
      

      But, you cannot use the $ delimiter when dealing with an image, because you have no assurances that your dataImage might not have a 0x24 byte in it.

    2. If you absolutely have to use the $ delimiter, then you have to do some encoding of your image data (such as via base64), to ensure it cannot have an occurrence of your delimiter in it. For example:

      // fill `NSData` with contents of the image data
      
      NSString *path = [[NSBundle mainBundle] pathForResource:@"Hello" ofType:@"png"];
      NSData *data = [NSData dataWithContentsOfFile:path];
      
      NSString *imageBase64String;
      
      // base64 encode the binary data into a string format
      
      if ([data respondsToSelector:@selector(base64EncodedStringWithOptions:)]) {
          imageBase64String = [data base64EncodedStringWithOptions:0]; // iOS 7+
      } else {
          imageBase64String = [data base64Encoding];                   // pre-iOS7
      }
      
      NSArray *array = @[@"Hello", @"World", imageBase64String];
      NSString *finalString = [array componentsJoinedByString:@"$"];
      NSData *finalData = [finalString dataUsingEncoding:NSUTF8StringEncoding];
      

      Clearly, if you base-64 encode the image when you add it to your $ delimited NSData, then at the other end, you have to decode the base-64 string back into an image at the other end. But hopefully this illustrates the idea.

    3. In your subsequent comments, you've said that you're going through this exercise to exchange the image with a server. Generally developers prefer to use well-established formats like JSON or XML to exchange data with a server. For example, considering the array object from my previous example, you could then create a NSData with a JSON representation as

      NSError *error = nil;
      NSData *finalData = [NSJSONSerialization dataWithJSONObject:array options:0 error:&error];
      NSAssert(finalData, @"Unable to dataWithJSONObject: %@", error);
      

      That would generate a payload that would look like:

      ["Hello","World","iVBORw0KGgoAAAANSUh ... +Jyutq4AAAAASUVORK5CYII="]
      

      Or, better, you might use a dictionary (which makes the functional purpose of each of the elements clear), but hopefully you get the idea: Consider using a well-established standard, like JSON, for creating your payload for your server.

    4. While your subsequent comments have made it clear that you're doing this to exchange an image with a server, if you were trying to put these three objects, two strings and an image, into a NSData to be saved locally in your app, a more efficient mechanism would be to use an archive. Thus, to create a NSData archive, you can:

      NSArray *array = @[@"Hello", @"World", [UIImage imageNamed:@"Hello.png"]];
      NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
      

      Then, to extract the results from this archive:

      NSArray *results = [NSKeyedUnarchiver unarchiveObjectWithData:data];
      

      For more information see the Archives and Serializations Programming Guide.

      But given your comments about wanting to send this to a server, an archive probably is not the right solution.