Good Clean iOS Core Data Stack
I was writing a new iOS 10+ only core data stack and thought I will show how simple the process has become.
DataCoordinator
I really like to have a singleton to manage the core data stack. Traditionally have always called this CoreDataManager. For now lets name it DataCoordinator.
final class DataCoordinator {
private static var coordinator: DataCoordinator?
public class func sharedInstance() -> DataCoordinator {
if coordinator == nil {
coordinator = DataCoordinator()
}
return coordinator!
} private init() {
}
}
3 Tier Architecture
TL;DR skip to next section. this is not required anymore.
Now we needed to setup a core data stack. we need to have 2 stacks or 2 contexts.
- for reading and displaying data in main thread.
- for writing data mostly from api calls and other background processes
This is best achieved by using a 3 tier architecture.
P1 is used to save to persistent store.
C1, C2 are child contexts of P1.
C2 is used to write from API’s. so that saved changes are propagated to C1 fast.
C1 changes are propagated to P1. as its a child context. the propagation is quick and as writing to persistent store happens in another context. operations in C1 is fairly fast. which is the whole goal of setting up the 3 tier architecture. performance!
All this is not required to be setup anymore.
NSPersistentContainer
iOS 10 has introduce a new NSPersistentContainer
class which reduced all the boilerplate code of setting up a 3 tier architecture to couple of lines below.
...public var container : NSPersistentContainer
private init() {
container = NSPersistentContainer(name: "Model")
container.loadPersistentStores(completionHandler:{(_, error) in
if let error = error {
fatalError("Unresolved error \(error)")
}
})
}
...
That’s it.
Container provides two ways to read and write.
- viewContext property
- performBackgroundTask method
Let’s add couple of convenience methods to help in reading and writing from container.
static func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) {
DataCoordinator.sharedInstance().container.performBackgroundTask(block)
}
static func performViewTask(_ block: @escaping (NSManagedObjectContext) -> Void) { block(DataCoordinator.sharedInstance().container.viewContext)
}
Reading
To read all we have to do is
DataCoordinator.performViewTask { (context) in
}
make sure to use DispatchQueue.main.async
or DispatchQueue.main.sync
before using this method when working from threads.
DispatchQueue.main.async {
DataCoordinator.performViewTask { (context) in
}
}
Writing
To write all we have to do is
DataCoordinator.performBackgroundTask { (context) in
}
You can find the entire code below.