Research Group for Applied Software Engineering
Forschungsgruppe für Angewandte Softwaretechnik

“Tell me and I will forget.

Show me and I will remember.

Involve me and I will understand.

Step back and I will act.”

 

iOS Data Serialization Tutorial

  

What is Object Serialization?

Object Serialization is converting structured data into sequential representation. Basically it means to preserve the current state (attributes, values etc.) of objects with the intention of using that later to reconstruct the objects. Serialization is used for data transfer between applications via network as well as within the same device. It is also used for storing data in a database or persisting in a file. All the famous and popular apps in today’s market uses serialization. Facebook, Twitter, Google - anyone who needs to exchange data between different systems or persist it.

 

Overview

There are three major approaches for data serialization in Objective-C:

  1. Property Lists
  2. JSON
  3. XML


And we are going to cover all of them in this tutorial. But before we get into the detail of these techniques, let’s take a look into the demo app, Cookbook, in which we will implement the serialization techniques. It’s pretty simple:

  • The Cookbook app maintains a list of recipes
  • The recipes should be stored in the file-system of the device (iPhone) and loaded from there
  • This storing of data will require serialization and the loading will require de-serialization.

 

If you download and run the application, it will not show any recipe. Because the serialization methods are not implemented and there is no data in the app. After you finish this tutorial your app will be able to create and edit recipe and also view the list of recipes.
If you do not have access to the demo project, you can just follow the steps and adopt it to your project.

 

Serialization Techniques

Property Lists

A property list (a.k.a. pList) is a structured data representation used by Cocoa and Core Foundation as a convenient way to store, organize, and access standard types of data. You can build little more complex structures but not that much. NSString, NSData, NSNumber, NSDate, NSArray, NSDictionary - these are the only Objective-C data types that property list supports. Sub-entries are only allowed with NSDictionary or NSArray. A property list is used to store "less than a few hundred kilobytes" of data. A very significant and popular use of property lists is to define application settings.

If you open a pList with Xcode this is how it will show a very comfortable view of key-value pairs. But if you open the pList with a text editor you will see the actual format, which is XML. Property list is a subset of XML.

 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
          <array>
                    <dict>
                              <key>id</key>
                              <integer>0</integer>
                              <key>imagePath</key>
                              <string>image5.png</string>
                              <key>ingredients</key>
                              <string>Cheese, cake</string>
                              <key>process</key>
                              <string>Mix cheese with cake and serve it.</string>
                              <key>timeEffort</key>
                              <integer>5</integer>
                              <key>title</key>
                              <string>Cheese Cake</string>
                    </dict>
          </array>
</plist>

 

Now let’s get into the implementation of Property list serialization. To implement it in our Cookbook app, you only need to work in the storeRecipes: method in CBPListSerialization.m. The necessary steps for serialization are as follows:

  1. Create a proper structure of data. Arrange entity objects in a structure of the allowed data types and convert all non-supported data types (if necessary).
    NSMutableArray *plist = [[NSMutableArray alloc] init];
    for (CBRecipe *recipe in recipes) {
        [plist addObject:@{@"id": recipe.rId,
                                    @"title": recipe.title,
                                    @"ingredients": recipe.ingredients,
                                    @"timeEffort": recipe.timeEffort,
                                    @"process": recipe.process,
                                    @"imagePath": recipe.imagePath}];

 

  1. Call method dataWithPropertyList:format:options:error: from class NSPropertyListSerialization

    NSError *error;

    NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:plist
                                                                                              format:NSPropertyListXMLFormat_v1_0
                                                                                             options:0
                                                                                                 error:&error];

    Don’t worry about the options parameter. It is 0, because it is currently unused. And for the format parameter, XML-format is the recommended one.

  2. Write the returned NSData to a file

    NSString *path = [[CBSerializationController applicationDocumentsDirectory].path
                                                     stringByAppendingPathComponent:@"Recipes.plist"];

    [plistData writeToFile:path atomically:YES];

The third step is optional. You can do whatever you want with the data. But for following this entire tutorial you should save the serialized data in a file. Now if you run the application and insert a recipe from Recipe screen, the data should be serialized and nicely saved in Property list format. Check the Recipes.plist file in the Documents directory of your application.

But you will not be able to see your recipe in the home screen of the Cookbook app, not yet. To achieve that you will have to de-serialize this Recipes.plist. You only need to complete loadRecipesFromFile: method in CBPListSerialization.m file. Here are the steps you will have to follow:

  1. First, read serialized data from file (Recipes.plist)

    NSString *path = [[CBSerializationController applicationDocumentsDirectory].path
                                                     stringByAppendingPathComponent:@"Recipes.plist"];

    NSData *plistData = [NSData dataWithContentsOfFile:path];

  2. Call method propertyListWithData:format:options:error: from class NSPropertyListSerialization

    NSError *error;

    NSArray *plist = [NSPropertyListSerialization propertyListWithData:plistData
                                                                                       options:NSPropertyListImmutable
                                                                                        format:NULL
                                                                                          error:&error];

    Set NSPropertyListImmutable for options parameter, which will cause the returned property list to contain immutable objects. And the format parameter contains the format that the property list was stored in. Pass NULL because we do not need to know the format.
  1. Reconstruct entity objects

    NSMutableArray *recipeArray = [[NSMutableArray alloc] init];

    for (NSDictionary *dict in plist) {

            [recipeArray addObject:[[CBRecipe alloc] initWithId:dict[@"id"]
                                                                               title:dict[@"title"] 
                                                                     ingredients:dict[@"ingredients"] 
                                                                          process:dict[@"process"]
                                                                       timeEffort:dict[@"timeEffort"]
                                                                      imagePath:dict[@"imagePath"]]];

    }

Now run the application. You should be able to see your recipe on the home screen of the app.

Cool, right?! You can add another recipe and check if multiple recipes are shown properly in the home screen.

Now that we have mastered (!) the property list, let’s move into the JSON serialization. Why? Because, you cannot always use the property list for data serialization. This approach has some limitations. We will compare the different serialization techniques at the end of the tutorial. For now let’s focus on JSON.

 

JSON

JSON means JavaScript Object Notation. Among the serialized data formats, JSON is most lightweight. JSON supports NSString, NSDictionary, NSNumber, NSArray, NSNull data types of Objective-C. When serializing in JSON, the Root Element must be NSDictionary or NSArray. This is how a JSON string looks like:

[

  {

    "id" : 0,

    "title" : "Cheese Cake",

    "timeEffort" : 5,

    "ingredients" : "Cheese, cake",

    "process" : "Mix cheese with cake and serve it.",

    "imagePath" : "image5.png"

  }

]



In the serialized JSON string, the [] Brackets implies an array and the {} Braces implies a dictionary.

Even though JSON has a significant difference from pList by being system-independent, the implementation of JSON serialization requires almost the same amount of work as pList. For implementing the object/data serialization using JSON, you will have to complete following steps. You will need to complete the storeRecipes: method this time in CBJSONSerialization.m file.

  1. Create a JSON-conform object by arranging entity objects in a structure of the allowed data types. Convert all not-supported data types (if necessary).

    NSMutableArray *jsonArray = [[NSMutableArrayalloc] init];

    for (CBRecipe *recipe in recipes) {

        [jsonArray addObject:@{@"id": recipe.rId,
                                           @"title": recipe.title,
                                           @"ingredients": recipe.ingredients,
                                           @"timeEffort": recipe.timeEffort,
                                           @"process": recipe.process,
                                           @"imagePath": recipe.imagePath}];

    }

 

  1. Call method dataWithJSONObject:options:error: from class NSJSONSerialization

    NSError *error;

    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonArray
                                                                                    options:NSJSONWritingPrettyPrinted
                                                                                       error:&error];

           Set NSJSONWritingPrettyPrinted as the value of options parameter to make the output easier to read.

  1. Write the returned NSData to a file

    NSString *path = [[CBSerializationController applicationDocumentsDirectory].path
                                                     stringByAppendingPathComponent:@"Recipes.json"];

    [jsonData writeToFile:path atomically:YES];

Again, the last step is optional in general but required for this tutorial. You will also have to modify the following code in CBSerializationController.m file so that the application uses the JSON serialization instead of a property list.

+ (id<CBSerializationStrategy>)getStrategy

{
//    return [[CBPListSerialization alloc] init];
      return [[CBJSONSerialization alloc] init];
//    return [[CBXMLSerialization alloc] init];
}

That is all for serialization. If you run the application and insert a new recipe, the objects should be serialized and saved in a JSON file in the Documents directory of your application. Now we will need to de-serialize this data to view the recipe in the home screen.

For de-serializing, you only need to work in the loadRecipesFromFile: method in CBJSONSerialization.m file. Just as pList serialization, first we will have to read serialized data from file:

  1. Read data from file

    NSString *path = [[CBSerializationController applicationDocumentsDirectory].path
                                                     stringByAppendingPathComponent:@"Recipes.json"];

    NSData *jsonData = [NSData dataWithContentsOfFile:path];

 

  1. Call method JSONObjectWithData:options:error: from class NSJSONSerialization

    NSError *error;

    NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:jsonData      
                                                                                      options:NSJSONReadingMutableContainers
                                                                                         error:&error];


           NSJSONReadingMutableContainers in the options parameter specifies that arrays and dictionaries are created as mutable objects. Not important for our app.

  1. Reconstruct entity objects

    NSMutableArray *recipeArray = [[NSMutableArray alloc] init];

    for (NSDictionary *dict in jsonArray) {

            [recipeArray addObject:[[CBRecipe alloc] initWithId:dict[@"id"]
                                                                               title:dict[@"title"] 
                                                                     ingredients:dict[@"ingredients"] 
                                                                          process:dict[@"process"]
                                                                       timeEffort:dict[@"timeEffort"]
                                                                      imagePath:dict[@"imagePath"]]];

    }

Now if you run the application, your recipe should be visible on the home screen of the app. Try adding more recipes and check if they are serializing and de-serializing properly. Next, we will implement the last serialization technique, using XML.

 

XML

XML stands for EXtensible Markup Language. It is a markup language much like HTML. It is designed to be self-descriptive. Its tags are not predefined. User must define his/her own tags. This is an example of an XML file:

<?xml version="1.0" encoding="UTF-8"?>
<recipes>
          <recipe id="0">
                    <title>Cheese Cake</title>
                    <ingredients>Cheese, cake</ingredients>
                     <process>Mix cheese with cake and serve it.</process>
                    <timeEffort>5</timeEffort>
                    <imagePath>image5.png</imagePath>
          </recipe>
</recipes>



When talking about the serialization and de-serialization (especially de-serialization) using XML, we should consider different XML parsers. Mainly there are 2 XML parsers – DOM and SAX. Before going into the details of implementation, we will first briefly explore these.

 

DOM (Document Object Model) Parser

  • Entire XML is parsed and tree structure is created
  • Easier to use than SAX parser
  • Keeps XML in memory
    • Repeatable queries possible
    • Memory consumption might become critical

 

SAX (Simple API for XML) Parser

  • Events are triggered during parsing (e.g. on begin/end tag)
  • Faster than DOM parser
  • Less memory usage
  • Doesn’t keep the XML in memory
  • More difficult to implement than DOM parsers

 

In this tutorial, we are going to use SAX parser. We will use NSXMLParser of Core Foundation framework which implements SAX parser. But before we get into the de-serialization, first we will have to serialize our objects into XML data. As there is no built-in class for XML serialization in iOS, we will have to serialize the data into XML format manually:

  1. Think about the XML structure of your objects
  2. Construct the XML-string

    // Create the header

    NSString *xmlString = @"<?xml version=\"1.0\" encoding=\"UTF-8\"?>";

    // Create the root element

    xmlString = [xmlString stringByAppendingString: @"<recipes>"];

    // Add recipes

    for (CBRecipe *recipe in recipes) {

        xmlString = [xmlString stringByAppendingString:
                                         [NSString stringWithFormat:@"<recipe id=\"%@\">
                                                                                         <title>%@</title>
                                                                                         <ingredients>%@</ingredients>
                                                                                         <process>%@</process>
                                                                                         <timeEffort>%@</timeEffort>
                                                                                         <imagePath>%@</imagePath>
                                                                                      </recipe>",
                                          recipe.rId, recipe.title, recipe.ingredients, recipe.process,
                                          recipe.timeEffort, recipe.imagePath]];

    }
  3. Write the string to a file

    NSString *path = [[CBSerializationController applicationDocumentsDirectory].path
                                                     stringByAppendingPathComponent:@"Recipes.xml"];

    [xmlString writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];

You will also have to modify the following code in CBSerializationController.m file again so that the application uses the XML serialization instead of JSON.

+ (id<CBSerializationStrategy>)getStrategy

{
//    return [[CBPListSerialization alloc] init];
//    return [[CBJSONSerialization alloc] init];
       return [[CBXMLSerialization alloc] init];
}


Run the application and insert a new recipe. Go to the Documents directory of the application and open the Recipes.xml file. Check if the data has been serialized properly.

Now that we are done with the serialization, we can get into the detail of de-serialization, which is quite different than the previous de-serialization approaches. For de-serialization we will have to implement the necessary delegate methods of NSXMLParser Class. Here are some important delegate methods and their functionality:

- (void)parser:(NSXMLParser*)parser didStartElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qName attributes:(NSDictionary*)attributeDict

  • Called when begin-tag appears (e.g. <recipe>)
  • Used to initialize objects
  • The 'attributes' parameter gives you all the attributes that are in the tag


- (void)parser:(NSXMLParser*)parser didEndElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qName

  • Called when end-tag appears (e.g. </recipe>)
  • Means that a specific object is finished parsing now
  • Add the object to the place where it belongs

 

- (void)parser:(NSXMLParser*)parser foundCharacters:(NSString*)string

  • Called whenever a string outside of tags appears (i.e. content)
  • Keep the value until you know what attribute it belongs to (indicated by the next end-tag you find)

 

- (void)parser:(NSXMLParser*)parser parseErrorOccurred:(NSError*)parseError

  • Called whenever a error occurs in the parser
  • Log the error and try to debug it

 

Here are the steps to implement the XML de-serialization:

  • Create a NSURL to the file

    NSString *path = [[CBSerializationControllerapplicationDocumentsDirectory].path
                                                     stringByAppendingPathComponent:@"Recipes.xml"];

    NSURL *xmlURL = [NSURLfileURLWithPath:path];

  • Set up the parser
    • We are using the SAX parser in the class NSXMLParser
    • It uses a protocol (NSXMLParserDelegate) you have to implement for event handling so make sure you add the protocol to CBXMLSerializer.h

      @interface CBXMLSerialization : NSObject <CBSerializationStrategy, NSXMLParserDelegate>

    • Then we initialize the parser and set us as it’s delegate

      NSXMLParser *recipeParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];

      [recipeParser setDelegate:self];
      [recipeParser setShouldProcessNamespaces:NO];
      [recipeParser setShouldReportNamespacePrefixes:NO];
      [recipeParser setShouldResolveExternalEntities:NO];

  • Construct your objects step by step while you handle the events the parser creates.
    To do that you first have to declare some instance variables. Because we have need the values in different methods:

    @implementation CBXMLSerialization {

        NSMutableArray *recipeArray;
        CBRecipe *currentRecipe;
        NSMutableString *elementValue;
        BOOL errorParsing;
        NSNumberFormatter *numberFormatter;

    }

    - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict

    {

        elementValue = [[NSMutableStringalloc] init];

        if ([elementName isEqualToString:@"recipes"]) {

            recipeArray = [[NSMutableArrayalloc] init];

        } elseif ([elementName isEqualToString:@"recipe"]) {

            currentRecipe = [[CBRecipealloc] init];

            currentRecipe.rId = [NSNumbernumberWithDouble:[attributeDict[@"id"] doubleValue]];

        }

    }

     -(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {

        if ([elementName isEqualToString:@"recipe"]) {

            [recipeArrayaddObject:currentRecipe];

        } elseif ([elementName isEqualToString:@"title"]) {

            currentRecipe.title = elementValue;

        } elseif ([elementName isEqualToString:@"ingredients"]) {

            currentRecipe.ingredients = elementValue;

        } elseif ([elementName isEqualToString:@"process"]) {

            currentRecipe.process = elementValue;

        } elseif ([elementName isEqualToString:@"timeEffort"]) {

            currentRecipe.timeEffort = [NSNumber numberWithDouble: [elementValue doubleValue]];

        } elseif ([elementName isEqualToString:@"imagePath"]) {

            currentRecipe.imagePath = elementValue;

        }

    }

     

    - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {

        [elementValueappendString:string];

    }

 

 

Comparison 

 

Figure 3: Comparison among Property list, JSON and XML serialization.

 

Summary

There are 3 main approaches for data serialization in iOS

  1. Property lists
  2. JSON
  3. XML

 

If you have followed this tutorial correctly you should know how to implement the serialization of your entity objects. You should also be able to decide which technique is appropriate in a specific scenario.