Case Study: Core Data, Parse MBaaS, and Parse Local Datastore In One App

With the availability of the Parse Local Datastore, when using a Parse backend (MBaaS) there is now a compelling reason to use the Parse Local Datastore instead of Core Data.

In this post I’ll explain how I’m using Parse backend, Parse Local Datastore, and continuing to use Core Data all in the same app, and provide some insight as to why I’ve chosen this approach for my iOS Swift app.

Here are some requirements for the app:

  1. No requirement for a user to login. Liken it to accessing a weather app.
  2. App serves up managed objects from the backend, and new instances are regularly added by the app provider and automatically available for users.
  3. App works offline when there is no internet connection and synchronizes with the backend when the connection is restored.
  4. User can add their own instances of the managed objects, but these instances are never written to the backend. Instead, they are stored locally and not shared with other users.
  5. Same common class must be used to represent data provided by the backend and custom data created by the user.

I’ve implemented the solution to take advantage of the new Parse Local Datastore. Anytime the app needs to fetch data from the backend and there isn’t a network connection, the data is fetched from the Parse Local Datastore. Enabling the local datastore is easy: add one line in AppDelegate before the code that sets the keys.

In theory, the user created data could also be stored in the Local Datastore, but I prefer to conceptualize that as a cache for backend data versus using it to store user created data that is only stored locally. Therefore, I’ve used Core Data (and SQLite) to store user created data on the device. If you wanted to use Parse Local Datastore for that purpose, it is fairly straightforward to save an object into the local store that you have created by simply using pinInBackground to add an object to the local datastore.

In this post, I’ll use a fictitious example of a Book object for illustration purposes.

The code above would only save the object in the local datastore. To synchronize it with the backend, it has to be saved for that purpose.

Objects stored in the backed and in Parse Local Datastore are PFObject subclasses, so the fictitious Book object I’ve shown above is a subclass of PFObject. It could be defined as follows whether it was being saved locally or in the backend.

PFObject subclasses automatically have createdAt and objectId properties.

Objects stored with Core Data are NSManagedObject subclasses. For the Book class, the subclass of NSManagedObject could be defined as I’ve shown below.

You’ll notice there is a createdAt property as well as a uuid. I’ll explain how those are used, along with the local property.

When a Book instance is fetched from the Parse backend (or local datastore in the case of no network connection), a helper method creates an instance of BookItem for each Book. The createdAt and objectId properties of the Parse Book are used to populate the createdAt and uuid properties of the BookItem. From that point, the app only manages the BookItem instances, some of which are only temporary objects representing the remote Parse data.

Note: I’ve defined uuid as the primary key so that instances of BookItem can be created for the Book instances, with the objectId from Book used as the primary key of BookItem. This means that the app is handling all BookItem instances with a uuid primary key, even though in the Parse backend, the Book objectId is the key.

When creating an instance of BookItem, the uuid value is created as follows.

The local property denotes whether an object originated from the backend or is user defined data, and thus local is false for all data stored in the Parse backend.

The transformed data should never be saved along with user created instances that are stored on the device. To guarantee this, the BookItem instances created for the Parse Book objects are created in a separate ManagedObjectContext that is never saved. I call it a tempManagedObjectContext, which is just what it says; a temporary or scratchpad ManagedObjectContext that is ultimately discarded. Here is how it is defined.

Note: There is a good discussion on the concept of using a temporary ManagedObjectContext in this StackOverflow article, which included posts from Marcus Zarra (from Cocoa Is My Girlfriend blog and author of an excellent Core Data book).

To create the instance of BookItem in the temporary context, a helper method could be as follows.

You might be wondering why I’m using guard for createdAt and objectId. Both of those properties are optional in PFObject. I believe they were not optional at one point in time, and am assuming they are now optional to accommodate the Parse Local Datastore, because when a PFObject (or subclass) is created and pinned to the local datastore, those properties do not have values until the object is saved to the backend. Since in my case I’m only retrieving these objects from the backend, I am expecting that these properties would always have values, however I don’t want to force unwrap them just in case, so I’ve chosen to make the return type of BookItem optional in this method, and if a Book is ever retrieved from the backend without objectId or createdAt values, then a BookItem will not be created. I’m definitely curious as to what others have done in this scenario because I’ve not seen much information on these properties being optional in later versions of the Parse SDK.

If you are curious as to how data is stored in the Parse Local Datastore, I’ll show an example of that with a Book object. First you can see the columns and rows Parse Local Datastore has created when I’ve added one Book.

Screen Shot 2015-09-17 at 1.09.04 PM

All of the data pertaining to one Book object is in the json column. Here is what it looks like.Screen Shot 2015-09-17 at 1.12.14 PM

This was a Book that was pinned locally and hadn’t been saved to the backend, so you can see the absence of objectId and createdAt. When you browse instances of Book that originated from the backend you will see those properties with values. Since I’m not storing user created data in the Parse Local Datastore in my app, this Book example of pinning locally is just to illustrate the absence of properties in this case, and to illustrate how the data is stored in the local datastore.

If you want to see details on how to browse the Parse Local Datastore, check out my previous post that covers that topic.

For peace of mind in terms of performance when doing queries for data stored on the device, I felt more comfortable to use Core Data, despite the fact that it meant having a separate class definition. I’ve not done performance tests using Parse Local Datastore so this is somewhat of a personal choice.

Continuing to use Core Data when using Parse Local Datastore may not be the right choice for every app, but in this particular scenario it seemed like the logical choice based on requirements. Being able to use the app seamlessly without a network connection by using the Parse Local Datastore as a cache is definitely nice and so far seems to be very slick, so I like what it offers.

Have you used Parse Local Datastore as a permanent store of data on the device, or have you used it as a cache for keeping the app functional when offline, but deferred to Core Data or NSKeyedArchiver for permanent storage on the device?

This blog is all about learning and sharing, so I’m definitely interested in any thoughts on the strategy I’ve described, or any alternative strategies.

Leave a Comment: