Simplified Core Data Stack using Mantle, Overcoat, and Magical Record in union

461 views Asked by At

I've designed a core data stack based on this blog post (in Swift) where I have two NSManagedObjectContext instances, a Main Queue Context (NSMainQueueConcurrencyType) and a Private Queue Context (NSPrivateQueueConcurrencyType) where the main context's job is to deal with all things user interaction related (editing, presenting data to the user) and the private context's only job is to write to disk.

To make managing the stack as easy as possible, I've integrated Magical Record, Overcoat, and Mantle. I've separated all this into two classes, a Core Data singleton stack (built on Magical Record) and a network manager singleton (Built on Overcoat which in turn is built on Mantle).

The Core Data stack looks like this:

import UIKit
import CoreData
import MagicalRecord

class CoreData: NSObject {

    enum StackType: Int {
        case Default, AutoMigrating, iCloud, inMemory
    }

    static let sharedStack = CoreData()
    private override init() {}

    var type: StackType = .Default

    func setupStackWithType(type: StackType, withName name: String = MagicalRecord.defaultStoreName()) {
        self.type = type
        switch self.type {
        case .Default:
            MagicalRecord.setupCoreDataStackWithStoreNamed(name)
        case .AutoMigrating:
            MagicalRecord.setupCoreDataStackWithAutoMigratingSqliteStoreNamed(name)
        case .iCloud:
            if let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first {
                let url = NSURL(fileURLWithPath: documentsPath)
                MagicalRecord.setupCoreDataStackWithiCloudContainer(name, localStoreAtURL: url)
            } else {
                print("Error: could not find documents directory")
            }
        case .inMemory:
            MagicalRecord.setupCoreDataStackWithInMemoryStore()
        }
    }

    func setupStackWithStoreName(storeName: String, automigrating: Bool = true) {
        if automigrating {
            MagicalRecord.setupCoreDataStackWithAutoMigratingSqliteStoreNamed(storeName)
        } else {
            MagicalRecord.setupAutoMigratingCoreDataStack()
        }
    }

    func saveWithBlock(block: (NSManagedObjectContext!) -> ()) {
        MagicalRecord.saveWithBlock(block, completion: {
            (success, error) in

        })
    }

    func cleanUp() {
        MagicalRecord.cleanUp()
    }

    var managedObjectContext: NSManagedObjectContext {
        return NSManagedObjectContext.MR_defaultContext()
    }
    var privateContext: NSManagedObjectContext {
       return NSManagedObjectContext.MR_rootSavingContext()
    }
    var coordinator: NSPersistentStoreCoordinator {
        return NSPersistentStoreCoordinator.MR_defaultStoreCoordinator()
    }

    var persistentStore: NSPersistentStore {
        return NSPersistentStore.MR_defaultPersistentStore()
    }
}

My network manager looks like:

import UIKit
import Overcoat
import MTLManagedObjectAdapter

class NetworkManager: OVCManagedHTTPSessionManager {

    static let singleton = NetworkManager(baseURL: NSURL(string: Config.ServerBaseEndpoint), managedObjectContext: nil, sessionConfiguration: {
        let config = NSURLSessionConfiguration.defaultSessionConfiguration()
        config.timeoutIntervalForRequest = 15
        config.timeoutIntervalForResource = 15
        return config
    }())

    private override init(baseURL url: NSURL?, managedObjectContext context: NSManagedObjectContext?, sessionConfiguration configuration: NSURLSessionConfiguration?) {
        super.init(baseURL: url, managedObjectContext: context, sessionConfiguration: configuration)
        self.responseSerializer.acceptableContentTypes = ["text/html", "application/json", "application/xml", "image/png"]
        self.securityPolicy = AFSecurityPolicy(pinningMode: .None)
        self.securityPolicy.allowInvalidCertificates = true
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    // MARK: - OVCHTTPSessionManager

    override class func modelClassesByResourcePath() -> [String: AnyClass] {
        return [Types.RestApi.Post.rawValue:Post.self, "\(Types.RestApi.Post.rawValue)/*": Post.self]
    }
 }

What I can't quite wrap my head around is 1) how these two classes can work in conjunction and 2) in regards to the core data stack, which context to save on, what work do on on which context, etc.

For NetworkManager.swift (which takes a NSManagedObjectContext to persist models to):

Which context do I initialize the manager with? My assumption would be if you make a network request and that JSON gets transformed into intermediate Mantle models and from there into NSManagedObejct instances, those instances should be saved right to the Private Queue Context, bypassing the Main Queue Context altogether in this instance.

When talking about CoreData.swift:

1) Magical Record has the saveWithBlock method which creates a local context and propagates it up to the root context (in this case the root context is the Private Queue Context) but it's unclear to be what work should be done inside the block.

In their documentation, they give this example:

Person *person = ...;

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){

  Person *localPerson = [person MR_inContext:localContext];
  localPerson.firstName = @"John";
  localPerson.lastName = @"Appleseed";

}];

where they create a Person object outside of the save block and then re-make the entity in the local context then edit it's properties. However in my case, all Person objects would be an instance of MTLModel, not NSManagedObject. When I create some model object, since it isn't a core data object, it wouldn't be inserted into any sort of context until I use MTLManagedObjectAdapter to transform the model into an NSManagedObject instance.

The best way to go about it seems to be create the MTLModel instance(s), do whatever editing is needed, and then either 1) inside saveWithBlock insert the newly created managed objects right into the local context and let it propagate up or 2) insert the objects into the Private Queue Context and save.

2) Do I really need to use the Main Queue Context at all for saving and editing? As I said before, Mantle has the model classes as a subclass of MTLModel and later maps them into NSManagedObject instances so it makes sense that I could just save directly to the Private Queue Context (whose only job is to write to disk anyway)

3) If I don't need to use the Main Queue Context for saving/editing, wouldn't it become the context I use for fetching NSManagedObjects (given that the Private Queue's job is to write to disk and the saving/editing functions of the Main Queue Context seem to be made obsolete by Mantle's intermediate model structure)?

0

There are 0 answers