Implemented the PersistenceService service.

This commit is contained in:
Javier Cicchelli 2023-04-11 00:38:11 +02:00
parent f86ffb8511
commit 510aa53f0e
2 changed files with 237 additions and 0 deletions

View File

@ -0,0 +1,133 @@
//
// PersistenceService.swift
// Persistence
//
// Created by Javier Cicchelli on 10/04/2023.
// Copyright © 2023 Röck+Cöde. All rights reserved.
//
import CoreData
public struct PersistenceService {
// MARK: Properties
public static let shared = PersistenceService()
public static let inMemory = PersistenceService(inMemory: true)
public let container: NSPersistentContainer
// MARK: Initialisers
init(inMemory: Bool = false) {
guard
let modelURL = Bundle.module.url(forResource: .Model.name, withExtension: .Model.extension),
let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL)
else {
fatalError("Could not load the model from the library.")
}
container = NSPersistentContainer(
name: .Model.name,
managedObjectModel: managedObjectModel
)
setContainer(inMemory)
}
// MARK: Functions
/// Create a private queue context.
/// - Returns: A concurrent `NSManagedObjectContext` context instance ready to use.
public func makeTaskContext() -> NSManagedObjectContext {
let taskContext = container.newBackgroundContext()
taskContext.automaticallyMergesChangesFromParent = true
taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return taskContext
}
/// Create a child context of the view context.
/// - Returns: A generated child `NSManagedObjectContext` context instance ready to use.
public func makeChildContext() -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
context.parent = container.viewContext
context.automaticallyMergesChangesFromParent = true
return context
}
/// Save a given context,
/// - Parameter context: A `NSManagedObjectContext` context instance to save.
public func save(context: NSManagedObjectContext) {
guard context.hasChanges else {
return
}
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
/// Save a given child context as well as its respective parent context.
/// - Parameter context: A child `NSManagedObjectContext` context instance to save.
public func save(childContext context: NSManagedObjectContext) {
guard context.hasChanges else {
return
}
do {
try context.save()
guard
let parent = context.parent,
parent == container.viewContext
else {
return
}
try parent.performAndWait {
try parent.save()
}
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
// MARK: - Helpers
private extension PersistenceService {
func setContainer(_ inMemory: Bool) {
container.persistentStoreDescriptions = [
NSPersistentStoreDescription(url:
inMemory
? URL(fileURLWithPath: "/dev/null")
: NSPersistentContainer.defaultDirectoryURL().appending(path: "\(String.Model.name).sqlite")
)
]
container.loadPersistentStores { _, error in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
// MARK: - String+Constants
private extension String {
enum Model {
static let name = "Model"
static let `extension` = "momd"
}
}

View File

@ -0,0 +1,104 @@
//
// PersistenceServiceTests.swift
// PersistenceTests
//
// Created by Javier Cicchelli on 11/04/2023.
// Copyright © 2023 Röck+Cöde. All rights reserved.
//
import CoreData
import XCTest
@testable import Persistence
final class PersistenceServiceTests: XCTestCase {
// MARK: Properties
private var persistence: PersistenceService!
// MARK: Initialiser tests
func test_initByDefault() {
// GIVEN
// WHEN
persistence = .init()
// THEN
XCTAssertEqual(persistence.container.persistentStoreDescriptions.count, 1)
XCTAssertEqual(persistence.container.persistentStoreDescriptions.first?.type, NSSQLiteStoreType)
XCTAssertEqual(persistence.container.persistentStoreDescriptions.first?.url?.lastPathComponent, "Model.sqlite")
XCTAssertNotNil(persistence.container.viewContext)
XCTAssertTrue(persistence.container.viewContext.automaticallyMergesChangesFromParent)
}
func test_initWithInMemory() {
// GIVEN
// WHEN
persistence = .init(inMemory: true)
// THEN
XCTAssertEqual(persistence.container.persistentStoreDescriptions.count, 1)
XCTAssertEqual(persistence.container.persistentStoreDescriptions.first?.type, NSSQLiteStoreType)
XCTAssertEqual(persistence.container.persistentStoreDescriptions.first?.url?.absoluteString, "file:///dev/null")
XCTAssertNotNil(persistence.container.viewContext)
XCTAssertTrue(persistence.container.viewContext.automaticallyMergesChangesFromParent)
}
// MARK: Static properties tests
func test_shared() {
// GIVEN
persistence = .shared
// WHEN
// THEN
XCTAssertEqual(persistence.container.persistentStoreDescriptions.count, 1)
XCTAssertEqual(persistence.container.persistentStoreDescriptions.first?.type, NSSQLiteStoreType)
XCTAssertEqual(persistence.container.persistentStoreDescriptions.first?.url?.lastPathComponent, "Model.sqlite")
XCTAssertNotNil(persistence.container.viewContext)
XCTAssertTrue(persistence.container.viewContext.automaticallyMergesChangesFromParent)
}
func test_inMemory() {
// GIVEN
persistence = .inMemory
// WHEN
// THEN
XCTAssertEqual(persistence.container.persistentStoreDescriptions.count, 1)
XCTAssertEqual(persistence.container.persistentStoreDescriptions.first?.type, NSSQLiteStoreType)
XCTAssertEqual(persistence.container.persistentStoreDescriptions.first?.url?.absoluteString, "file:///dev/null")
XCTAssertNotNil(persistence.container.viewContext)
XCTAssertTrue(persistence.container.viewContext.automaticallyMergesChangesFromParent)
}
// MARK: Functions tests
func test_makeTaskContext() {
// GIVEN
persistence = .inMemory
// WHEN
let context = persistence.makeTaskContext()
// THEN
XCTAssertTrue(context.automaticallyMergesChangesFromParent)
XCTAssertTrue(context.mergePolicy as AnyObject === NSMergeByPropertyObjectTrumpMergePolicy)
XCTAssertNil(context.parent)
}
func test_makeChildContext() {
// GIVEN
persistence = .inMemory
// WHEN
let context = persistence.makeChildContext()
// THEN
XCTAssertTrue(context.automaticallyMergesChangesFromParent)
XCTAssertTrue(context.mergePolicy as AnyObject === NSMergeByPropertyObjectTrumpMergePolicy)
XCTAssertEqual(context.parent, persistence.container.viewContext)
}
}