Posts in core data

How to Abstract Your Persistence Layer and Migrate to Another One on iOS With JustPersist

The original post is published on the JUST EAT tech blog at the following URL http://tech.just-eat.com/2017/03/02/how-to-abstract-your-persistence-layer-and-migrate-to-another-one-on-ios-with-justpersist/

JustPersist Banner

In this blog post we introduce a solution to deal with data persistence. We developed it for the Just Eat iOS app and we call it JustPersist. It’s available open source on Github at github.com/justeat/JustPersist.

JustPersist aims to be the easiest and safest way to do persistence on iOS with Core Data support out of the box. It also allows you to migrate to any new persistence framework with minimal effort.

I highly suggest to read my previous article The Easiest Core Data as the underlying concepts are explained there.

The main author behind JustPersist and its design is Keith Moon. Major kudos to Keith for the excellent execution in Swift!

Overview

At Just Eat, we persist a variety of data in the iOS app. In 2014 we decided to use MagicalRecord as a wrapper on top of Core Data but over time the numerous problems and fundamental thread-safety issues, arose. In 2017, MagicalRecord is not supported anymore and new solutions look more appealing. We decided to adopt Skopelos: a much younger and lightweight Core Data stack, with a simpler design, developed by Alberto De Bortoli, one of our engineers. The design of the persistence layer interface gets inspiration from Skopelos as well, and we invite the reader to take a look at its documentation.

The main problem in adopting a new persistence solution is migrating to it. It is rarely easy, especially if the legacy codebase doesn’t hide the adopted framework (in our case MagicalRecord) but rather spread it around in view controllers, managers, helper classes, categories and sometimes views. Ultimately, in the case of Core Data, there is a single persistent store and this is enough to make impossible to move access across “one at a time”. There can only be one active persistence solution at a time.

We believe this is a very common problem, especially in the mobile world. We created JustPersist for this precise reason and to ease the migration process.

At the end of the day, JustPersist is two things:

  • A persistence layer with a clear and simple interface to do transactional readings and writings (Skopelos-style)
  • A solution to migrate from one persistence layer to another with (we believe) the minimum possible effort

JustPersist aims to be the easiest and safest way for persistence on iOS. It supports Core Data out of the box and can be extended to transparently support other frameworks. Since moving from MagicalRecord to Skopelos, we provide available wrappers for these two frameworks.

The tone of JustPersist is very much Core Data-oriented but it enables you to migrate to any other persistence framework if a custom data store (wrapper) is implemented (in-memory, key-value store, even Realm if you are brave enough).

JustPersist is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod "JustPersist/Skopelos"
# or
pod "JustPersist/MagicalRecord"

Using only pod JustPersist will add the core pod with no subspecs and you’ll have to implement your own wrapper to use the it. If you intend to extend JustPersist to support other frameworks, we suggest creating a subspec.

Usage of the persistence layer

To perform operation you need a data store, which you can setup like this (or see related paragraph paragraph):

let dataStore = SkopelosDataStore(sqliteStack: <modelURL>)
// or
let dataStore = MagicalRecordDataStore()

Before using the data store for the first time, you must call setup() on it, and possibly tearDown() when you are completely done with it.

We suggest setting up the stack at app startup time, in the applicationDidFinishLaunchingWithOptions method in the AppDelegate and to tear it down at the end of the life cycle of your entire app, when resetting the state of the app (if you provide support to do so) or in the tearDown method of your unit tests suite.

To hide the underlying persistence framework used, JustPersist provides things that conform to DataStoreItem and MutableDataStoreItem, rather than the CoreData specific NSManagedObject. These protocols provide access to properties using objectForKey and setObject:forKey: methods.

In the case of Core Data, JustPersist provides an extension to NSManagedObject to make it conforming to MutableDataStoreItem.

Readings and writings

The separation between readings and writings is the foundation of JustPersist. Reading are always synchronous by design:

dataStore.read { (accessor) in
  ...
}

While writings can be both synchronous or asynchronous:

dataStore.writeSync { (accessor) in
  ...
}

dataStore.writeAsync { (accessor) in
  ...
}

The accessor provided by the blocks can be a read one (DataStoreReadAccessor) or a read/write one (DataStoreReadWriteAccessor). Read accessors allow you to do read operations such as:

func items(forRequest request: DataStoreRequest) -> [DataStoreItem]
func firstItem(forRequest request: DataStoreRequest) -> DataStoreItem?
func countItems(forRequest request: DataStoreRequest) -> Int

While the read/write ones allow you to perform a complete set of CRUD operations:

func mutableItems(forRequest request: DataStoreRequest) -> [MutableDataStoreItem]
func firstMutableItem(forRequest request: DataStoreRequest) -> MutableDataStoreItem?
func createItem(ofMutableType itemType: MutableDataStoreItem.Type) -> MutableDataStoreItem?
func insert(_ item: MutableDataStoreItem) -> Bool
func delete(item: MutableDataStoreItem) -> Bool
func deleteAllItems(ofMutableType itemType: MutableDataStoreItem.Type) -> Bool
func mutableVersion(ofItem item: DataStoreItem) -> MutableDataStoreItem?

To perform an operation you might need a DataStoreRequest which can be customized with itemType, an NSPredicate, an array of NSSortDescriptor, offset and limit. Think of it as the corresponding Core Data’s NSFetchRequest.

Here are some complete examples:

dataStore.read { (accessor) in
  let request = DataStoreRequest(itemType: Restaurant.self)
  let count = accessor.countItems(forRequest: request)
}

dataStore.read { (accessor) in
  let request = DataStoreRequest(itemType: Restaurant.self)
  request.setFilter(whereAttribute: "name", equalsValue: <some_name>)
  guard let restaurant = accessor.firstItem(forRequest: request) as? Restaurant else { return }
  ...
}

dataStore.writeSync { (accessor) in
  let restaurant = accessor.createItem(ofMutableType: Restaurant.self) as! Restaurant
  restaurant.name = <some_name>
  ...
  let wasDeleted = accessor.delete(item: restaurant)
}

written in core data, database, ios, magicalrecord, open source, persistence, skopelos Read on →

The Easiest Core Data

Over the past months I spent a lot of time on Core Data, I had to deal with a project with a lot of legacy code, Core Data horros and multithreading violations. Core Data is hard, at times it can be frustrating and confusing. For this reasons, I decided to come up with a refined solution for a super simple design. The aim was to write a minimalistic, thread-safe, non-boilerplate and super easy to use version of Active Record on Core Data, that is actually all you need for doing Core Data in the 95% of the cases. The iterations were a few and I reconsidered my solution multiple times until I finally got where I wanted.

So… here it is. Let me introduce Skiathos and Skopelos. Skiathos is the Objective-C version, while Skopelos is the Swifty one. They are available as CocoaPods. The names come from 2 islands in Greece where I spent my 2016 summer holidays and found the inspiration to refine the final versions.

General notes

This component aims to have an extremely easy interface to introduce Core Data into your app with almost zero effort.

The design introduced here involves a few main components:

  • CoreDataStack
  • AppStateReactor
  • DALService (Data Access Layer)

CoreDataStack

If you have experience with Core Data, you might know that creating a stack is an annoying process full of pitfalls. This component is responsible for the creation of the stack (in terms of chain of managed object contexts) using the design described here by Marcus Zarra.

core-data-stack

An important difference from Magical Record, or other third-party libraries, is that the savings always go in one direction, from slaves down (or up?) to the persistent store. Other components allow you to create slaves that have the private context as parent and this causes the main context not to be updated or to be updated via notifications to merge the context. The main context should be the source of truth and it is tied the UI: having a much simpler approach helps to create a system easier to reason about.

written in active record, core data, ios, persistence Read on →