Pre-Loading App Content with Core Data

I recently worked on a travel guide app that required a considerable amount of content to be pre-loaded. Normally I’d suggest using parse.com as a nice reliable out of the box solution, but one of the constraints from the client was to not rely on web services - they wanted their users to have data from the first time they launch the app, and those users might not have data service. (This is a travel guide after all.)

I love constraints. Constraints bring the real challenge to your creativity and expertise.

The Way People Seem To Want To Do It

I learned a long time ago that the second step in building a solution is seeing whether anyone has found a solution already. (The first step is clearly defining the problem.) There’s a wealth of information out there on how to preload content for apps and the vast majority of the popular search results suggest the same design:

  1. Create data files with XML/JSON/CSV format data
  2. Write import code that matches matches your content to your data model
  3. Run in the simulator and then copy the resulting .sqlite file from the simulator into your project

Disappointment ensued:

  1. Its a manual and repetitive process that consumes developer time
  2. The import code was always project-/model-specific and can’t be re-used
  3. Changes to the content require every component in the system to be updated: data, model, and import code

It works, but I wouldn’t put it into a production app.

The Way I Wanted To Do It

I wanted a solution that addressed the three concerns I had. I want something that is automated and project/model agnostic - something that can be re-used in future projects.

I’d previously created a nice little CoreData singleton (much like everyone else has at some point) with a few categories and NSManagedObject helpers. My ideal solution was another category attached to that singleton that I could optionally include for mapping parsed JSON dictionaries into NSManagedObjects.

Mapping JSON Fields To Entity Attributes

Working with Core Data has some handy perks, one being that NSEntityDescription lets me inspect all of its attributes. In fact, it returns an NSDictionary containing the attributes and descriptions for each attribute, including the data type. I can iterate through all of the attributes for a given entity:

NSDictionary *attributes = [[managedObject entity] attributesByName];
for (NSString *attribute in attributes)
{
     NSAttributeType attributeType = [[attributes objectForKey:attribute] attributeType];
     switch (attributeType)
     {
          case NSStringAttributeType:
          {
               NSLog(@“%@ : String (%d), attribute, attributeType);
               break;
          }
          case NSInteger16AttributeType:
          case NSInteger32AttributeType:
          case NSInteger64AttributeType:
          {
               NSLog(@"%@ : Integer (%d)", attribute, attributeType);
               break;
          }
     }
     }
}

Elaborating on the above code, as long as my JSON model matches out NSManagedEntity model, I can match JSON key/value pairs to NSEntityDescription attribute/value pairs:

NSDictionary *attributes = [[managedObject entity] attributesByName];
for (NSString *attribute in attributes) 
{
     id value = [jsonDictionary objectForKey:attribute];
     if (value)
     {
          [managedObject setValue:value forKey:attribute];
     }
}

Of course, on the first execution of my new import code I was reminded that there’s a lot of JSON out there thats… dirty. Strings presented as numbers, numbers presented as strings, this is “perfectly acceptable” for JSON but not for Core Data. This is easily fixed by adding some type-safety to the attribute mapping:

...
for (NSString *attribute in attributes) 
{
     id value = [jsonDictionary objectForKey:attribute];
     if (value)
     {
          NSAttributeType attributeType = [[attributes objectForKey:attribute] attributeType];
          switch (attributeType)
          {
               case NSStringAttributeType:
               {
                    if ([value isKindOfClass:[NSNumber class]])
                    {
                        value = [value stringValue];
                    }
                    break;
               }
               // Process other attribute types......
               {}
          }
          [managedObject setValue:value forKey:attribute];

Conclusion

Overall I was happy with the results, but there was one glaring limitation that I observed: supporting many-to-many relationships. Due to JSON’s nested structure, defining an object with multiple “owners” can get tricky unless you’re willing to manually attach IDs to everything. Supporting that is something I’d like to add to this setup.

I’ve added it into my Core Data box-o-tricks, and you can find it up on my github.