Skip to content

Instantly share code, notes, and snippets.

@RobinDaugherty
Forked from kharrison/CoreDataController.swift
Last active July 15, 2018 23:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RobinDaugherty/58b074dd76bf83ec15f383e48034cc2a to your computer and use it in GitHub Desktop.
Save RobinDaugherty/58b074dd76bf83ec15f383e48034cc2a to your computer and use it in GitHub Desktop.
Swift wrapper for NSPersistentContainer - Easy Core Data Setup with iOS 10 and PromiseKit
//
// CoreDataController.swift
//
// Created by Keith Harrison http://useyourloaf.com
// Copyright (c) 2017 Keith Harrison. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
import Foundation
import CoreData
import PromiseKit
/// `CoreDataController` is a simple wrapper class for setting up
/// and using a Core Data stack.
///
/// Warning: Requires iOS 10 as it depends on the availability of
/// `NSPersistentContainer`.
@available(iOS 10.0, macOS 10.12, watchOS 3.0, tvOS 10.0, *)
public class CoreDataController: NSObject {
/// The default directory for the persistent stores on the current platform.
///
/// - Returns: An `NSURL` for the directory containing the persistent
/// store(s). If the persistent store does not exist it will be created
/// by default in this location when loaded.
public class func defaultDirectoryURL() -> URL {
return NSPersistentContainer.defaultDirectoryURL()
}
/// A read-only flag indicating if the persistent store is loaded.
public private (set) var isStoreLoaded = false
/// The managed object context associated with the main queue (read-only).
/// To perform tasks on a private background queue see
/// `performBackgroundTask:` and `newPrivateContext`.
///
/// The context is configured to be generational and to automatically
/// consume save notifications from other contexts.
public var viewContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
/// The `URL` of the persistent store for this Core Data Stack. If there
/// is more than one store this property returns the first store it finds.
/// The store may not yet exist. It will be created at this URL by default
/// when first loaded.
///
/// This is a readonly property to create a persistent store in a different
/// location use `loadStoreAtURL:withCompletionHandler`. To move an existing
/// persistent store use
/// `replacePersistentStoreAtURL:withPersistentStoreFromURL:`.
public var storeURL: URL? {
var url: URL?
let descriptions = persistentContainer.persistentStoreDescriptions
if let firstDescription = descriptions.first {
url = firstDescription.url
}
return url
}
/// A flag that indicates whether this store is read-only. Set this value
/// to YES before loading the persistent store if you want a read-only
/// store (for example if loading from the application bundle).
/// Default is false.
public var isReadOnly = false
/// A flag that indicates whether the store is added asynchronously.
/// Set this value before loading the persistent store.
/// Default is true.
public var shouldAddStoreAsynchronously = true
/// A flag that indicates whether the store should be migrated
/// automatically if the store model version does not match the
/// coordinators model version.
/// Set this value before loading the persistent store.
/// Default is true.
public var shouldMigrateStoreAutomatically = true
/// A flag that indicates whether a mapping model should be inferred
/// when migrating a store.
/// Set this value before loading the persistent store.
/// Default is true.
public var shouldInferMappingModelAutomatically = true
/// The type of store that will be used.
/// This allows us to use in-memory store for testing for example.
public var storeType = NSSQLiteStoreType
/// Creates and returns a `CoreDataController` object. This is the designated
/// initializer for the class. It creates the managed object model,
/// persistent store coordinator and main managed object context but does
/// not load the persistent store.
///
/// The managed object model should be in the same bundle as this class.
///
/// - Parameter name: The name of the persistent store.
///
/// - Returns: A `CoreDataController` object or nil if the model
/// could not be loaded.
public init?(name: String) {
let bundle = Bundle(for: CoreDataController.self)
guard let mom = NSManagedObjectModel.mergedModel(from: [bundle]) else {
return nil
}
persistentContainer = NSPersistentContainer(name: name, managedObjectModel: mom)
super.init()
}
/// When set before the persistent store is used, sets it to use
/// a non-persistent (in-memory) method of storage.
/// This is used while testing so that pre-existing data will be loaded
/// and the results of the test will not be written to storage and
/// thereby affect development.
public func useNonPersistentDataStore() {
self.storeType = NSInMemoryStoreType
}
/// Load the persistent store from the default location.
///
/// - Returns: Promise that is fulfilled when the loading of the persistent store has completed.
///
/// To override the default name and location of the persistent store use
/// `loadStoreAtURL:`.
public func loadStore() -> Promise<Void> {
return loadStore(atURL: storeURL)
}
/// Load the persistent store from a specified location
///
/// - Parameter storeURL: The URL for the location of the persistent
/// store. It will be created if it does not exist.
/// - Returns: Promise that is fulfilled when the loading of the persistent store has completed.
public func loadStore(atURL storeURL: URL?) -> Promise<Void> {
return Promise<Void> { seal in
if let storeURL = storeURL ?? self.storeURL {
let description = storeDescription(with: storeURL)
persistentContainer.persistentStoreDescriptions = [description]
}
persistentContainer.loadPersistentStores { (storeDescription, error) in
if error == nil {
self.isStoreLoaded = true
self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
seal.fulfill(())
} else {
seal.reject(error!)
}
}
}
}
/// A flag indicating if the persistent store exists at the specified URL.
///
/// - Parameter storeURL: An `NSURL` object for the location of the
/// peristent store.
///
/// - Returns: true if a file exists at the specified URL otherwise false.
///
/// - Warning: This method checks if a file exists at the specified
/// location but does not verify if it is a valid persistent store.
public func persistentStoreExists(at storeURL: URL) -> Bool {
if storeURL.isFileURL &&
FileManager.default.fileExists(atPath: storeURL.path) {
return true
}
return false
}
/// Destroy a persistent store.
///
/// - Parameter storeURL: An `NSURL` for the persistent store to be
/// destroyed.
/// - Returns: A flag indicating if the operation was successful.
/// - Throws: If the store cannot be destroyed.
public func destroyPersistentStore(at storeURL: URL) throws {
let psc = persistentContainer.persistentStoreCoordinator
try psc.destroyPersistentStore(at: storeURL, ofType: storeType, options: nil)
}
/// Replace a persistent store.
///
/// - Parameter destinationURL: An `NSURL` for the persistent store to be
/// replaced.
/// - Parameter sourceURL: An `NSURL` for the source persistent store.
/// - Returns: A flag indicating if the operation was successful.
/// - Throws: If the persistent store cannot be replaced.
public func replacePersistentStore(at url: URL, withPersistentStoreFrom sourceURL: URL) throws {
let psc = persistentContainer.persistentStoreCoordinator
try psc.replacePersistentStore(at: url, destinationOptions: nil,
withPersistentStoreFrom: sourceURL, sourceOptions: nil, ofType: storeType)
}
/// Create and return a new private queue `NSManagedObjectContext`. The
/// new context is set to consume `NSManagedObjectContextSave` broadcasts
/// automatically.
///
/// - Returns: A new private managed object context
public func newPrivateContext() -> NSManagedObjectContext {
return persistentContainer.newBackgroundContext()
}
/// Execute a block on a new private queue context.
///
/// - Parameter block: A block to execute on a newly created private
/// context. The context is passed to the block as a parameter.
/// - Returns: Promise that is fulfilled when the block has finished executing.
public func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) throws -> Void) -> Promise<Void> {
return Promise<Void> { seal in
persistentContainer.performBackgroundTask { context in
do {
try block(context)
seal.fulfill(())
} catch {
seal.reject(error)
}
}
}
}
/// Return an object ID for the specified URI representation if a matching
/// store is available.
///
/// - Parameter storeURL: An `NSURL` containing a URI of a managed object.
/// - Returns: An optional `NSManagedObjectID`.
public func managedObjectID(forURIRepresentation storeURL: URL) -> NSManagedObjectID? {
let psc = persistentContainer.persistentStoreCoordinator
return psc.managedObjectID(forURIRepresentation: storeURL)
}
private let persistentContainer: NSPersistentContainer;
private func storeDescription(with url: URL) -> NSPersistentStoreDescription {
let description = NSPersistentStoreDescription(url: url)
description.shouldMigrateStoreAutomatically = shouldMigrateStoreAutomatically
description.shouldInferMappingModelAutomatically = shouldInferMappingModelAutomatically
description.shouldAddStoreAsynchronously = shouldAddStoreAsynchronously
description.isReadOnly = isReadOnly
description.type = storeType
return description
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment