# CoreDataMiddleware ## Getting Started ### Prerequisites - You need to be familiar with [PersistenceMiddleware](../PersistenceMiddleware/README.md). - Just like in every *Core Data* related project, you need a `NSManagedObjectModel`, containing details about every entity. For example, a model with the name "MusicDB" has two entities *Song* and *Album* with an associated `NSManagedObject` for each of them. The *Song* entity has for simplicity reasons just one attribute *title*: ```swift @objc(ManagedSong) class ManagedSong: NSMangedObject { @NSManaged var title: String? } ``` - Every entity (that should be handled by a persistence middleware) must have a value-object as a counterpart that conforms to the `CoreDataPersistable` protocol. ```swift public protocol CoreDataPersistable { associatedtype PersistableObject associatedtype Request: CoreDataRequest where Request.ManagedObject == PersistableObject var managedObjectID: NSManagedObjectID? { get } init(_ persistableObject: PersistableObject) throws func update(_ managedObject: PersistableObject) throws -> PersistableObject static var managedObjectEntityName: String { get } } ``` For example, the counterpart for `ManagedSong` could be something like: ```swift struct Song: CoreDataPersistable { var title: String? let managedObjectID: NSManagedObjectID? init(title: String?) { self.title = title self.managedObjectID = nil } init(_ persistableObject: ManagedSong) throws { self.title = persistableObject.title self.managedObjectID = persistableObject.objectID } func update(_ managedObject: ManagedSong) throws -> ManagedSong { if managedObject.title != title { managedObject.title = title } return managedObject } static var managedObjectEntityName: String = "Song" typealias Request = SongRequest } ``` - Lastly, every `CoreDataPersistable` conforming object must have a matching object conforming to `CoreDataRequest`. ```swift enum SongRequest: CoreDataRequest { case all case allWithTitle(String) var fetchRequest: NSFetchRequest { let request: NSFetchRequest = ManagedSong.fetchRequest() switch self { case .allWithTitle(let title): request.predicate = NSPredicate(format: "%K == %@", "title", title) default: break } return request } var sortDescriptors: [NSSortDescriptor] { [ NSSortDescriptor(keyPath: \ManagedSong.title, ascending: true) ] } } ``` ### Creating the middleware The `CoreDataController` (which conforms to `PersistenceController`) is initialized with an object of type `CoreDataContainer`, which is a wrapper for either a `NSPersistentContainer` or a `NSPersistentCloudKitContainer`. ```swift let container = CoreDataContainer(NSPersistentContainer(name: "MusicDB")) let controller = CoreDataController(container) let middleware = controller.makeMiddleware() ``` **Note**: If you have more than one entity, every entity-controller (like: `CoreDataController`) must reference the same `CoreDataContainer`: ```swift let container = CoreDataContainer(NSPersistentContainer(name: "MusicDB")) let albumController = CoreDataController(container) let songController = CoreDataController(container) let songMiddleware = songController.makeMiddleware() let albumMiddleware = albumController.makeMiddleware() ``` ## Note regarding `CoreDataContainer` The implementation of `CoreDataContainer` does not entirely reflect the idea behind Redux and SwiftRex, since itself holds the loading state of the container and not the `AppState`. The reason why I chose the `CoreDataContainer` and not implemented a loading middleware is, that my main goal was to eliminate the need to dispatch a loading action. Every persistence action requires a loaded store and therefore a loading action seemed redundant. If you nonetheless want the loading state in your app state, this can be accomplished with an extension to the `PersistenceState`: ```swift enum ContainerLoadingState { case initial case loading case loaded case failed(Error) } extension PersistenceState where PersistenceError == CoreDataError { var containerLoadingState: ContainerLoadingState { // TODO } } ``` ### Other dismissed solutions - Create one middleware with the only reference to the `NSPersistentContainer`, which receives a closure as an action with the container as input parameter (TODO: Add an example).