4

I'm writing an application using the MVVM pattern. And I'm wondering know how to create the CoreData stack so it can be accessed from various places in my app.

First approach is to create a persistent container in the AppDelegate and then inject this service to my ViewModels (simultaneously passing the managedObjectContext as an environment variable to my Views).

This way, however, accessing context throughout the app is more difficult: e.g. in decoding network responses, as they don't have access to the managedObjectContext:

protocol APIResource {
    associatedtype Response: Decodable
    ...
}

extension APIResource {
    func decode(_ data: Data) -> AnyPublisher<Response, APIError> {
        Just(data)
            // how can I access context here to pass it to JSONDecoder?
            .decode(type: Response.self, decoder: JSONDecoder())
            .mapError { error in
                .parsing(description: error.localizedDescription)
            }
            .eraseToAnyPublisher()
    }
}

The other solution I've seen is to use a singleton. I can access it from anywhere in the project but how can I create it in the right way?

What if I wan't to modify some object in the main and the background queue at the same time? Or what if both queues want to modify the same object?

pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • 1
    Not an actual answer, but food for thoughts: IMHO, using Core Data when decoding network responses is not the right approach. I'd rather use [DTOs](https://en.wikipedia.org/wiki/Data_transfer_object) and then leave the responsibility of converting and storing them to someone else. – MrAsterisco May 03 '20 at 09:09
  • 1
    For those wonders how context can be injected into View and then View Model, https://stackoverflow.com/q/63959960/2226315 – XY L Sep 19 '20 at 04:44

1 Answers1

7

You can use Core Data Singleton class

import CoreData

class CoreDataStack {
    static let shared = CoreDataStack()

    private init() {}

    var managedObjectContext: NSManagedObjectContext {
        return self.persistentContainer.viewContext
    }

    var workingContext: NSManagedObjectContext {
        let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        context.parent = self.managedObjectContext
        return context
    }

    // MARK: - Core Data stack

    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "MyStuff")
        container.loadPersistentStores(completionHandler: { storeDescription, error in
            if let error = error as NSError? {
                RaiseError.raise()
            }
        })
        return container
    }()

    // MARK: - Core Data Saving support

    func saveContext() {
        self.managedObjectContext.performAndWait {
            if self.managedObjectContext.hasChanges {
                do {
                    try self.managedObjectContext.save()
                    appPrint("Main context saved")
                } catch {
                    appPrint(error)
                    RaiseError.raise()
                }
            }
        }
    }

    func saveWorkingContext(context: NSManagedObjectContext) {
        do {
            try context.save()
            appPrint("Working context saved")
            saveContext()
        } catch (let error) {
            appPrint(error)
            RaiseError.raise()
        }
    }
}

Core Data is not thread safe. If you write something on manageObject and don't want to save that, but some other thread save the context, then the changes that you don't want to persist will also persist.

So to avoid this situation always create working context - which is private.

When you press save, then first private context get saved and after that you save main context.

In MVVM you should have DataLayer through which your ViewModel interact with Core Data singleton class.

pawello2222
  • 46,897
  • 22
  • 145
  • 209
Jawad Ali
  • 13,556
  • 3
  • 32
  • 49
  • 1
    How would we use this with SwiftUI? Do you pass the context only via EnvironmentObject or do you pass the whole CoreDataStack? Thanks! – noloman Oct 27 '21 at 19:28