Skip to content

Instantly share code, notes, and snippets.

@kristopherjohnson
Created September 28, 2014 14:51
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kristopherjohnson/6083bfc1216cb7ae3761 to your computer and use it in GitHub Desktop.
Save kristopherjohnson/6083bfc1216cb7ae3761 to your computer and use it in GitHub Desktop.
Swift: Core Data persistence stack
import Foundation
import CoreData
// "Class variables" used in DataStore.sharedDataStore()
private var _instance: DataStore?
private var DataStoreClassLock = NSRecursiveLock()
private let appStoreFilename = "DataStore.sqlite"
private let testStoreFilename = "test_DataStore.sqlite"
/// Manages the Core Data stack
@objc public class DataStore {
public let errorDomain = "Data Store"
/// Get reference to the shared DataStore singleton
public class func sharedDataStore() -> DataStore {
return resultSynchronizedWithLock(DataStoreClassLock) {
if let existingInstance = _instance {
return existingInstance
}
else {
_instance = DataStore(filename: appStoreFilename, propagateChangesBetweenContexts: true)
return _instance!
}
}
}
/// Get DataStore instance to be used by unit tests
///
/// Each call creates a new empty datastore
public class func testDataStore() -> DataStore {
let dataStore = DataStore(filename: testStoreFilename, propagateChangesBetweenContexts: false)
var error: NSError? = nil
dataStore.deletePersistentStoreFile(&error)
if let e = error {
log(.Error, "unable to delete existing test data store", e)
}
return dataStore
}
let filename: String
let lock = NSRecursiveLock()
init(filename: String, propagateChangesBetweenContexts: Bool) {
self.filename = filename
if (propagateChangesBetweenContexts) {
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self,
selector: "contextDidSaveBackgroundQueueContext:",
name: NSManagedObjectContextDidSaveNotification,
object: self.backgroundQueueContext)
notificationCenter.addObserver(self,
selector: "contextDidSaveMainQueueContext:",
name: NSManagedObjectContextDidSaveNotification,
object: self.mainQueueContext)
}
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
// MARK: - Core Data stack
var applicationDocumentsDirectory: NSURL {
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return urls[urls.count-1] as NSURL
}
public var persistentStoreURL: NSURL {
return self.applicationDocumentsDirectory.URLByAppendingPathComponent(self.filename)
}
public var managedObjectModel: NSManagedObjectModel {
let modelURL = NSBundle.mainBundle().URLForResource("KWKWLaneClosures", withExtension: "momd")
return NSManagedObjectModel(contentsOfURL: modelURL!)
}
public var persistentStoreCoordinator: NSPersistentStoreCoordinator? {
return resultSynchronizedWithLock(lock) {
if let alreadyInitializedInstance = self._persistentStoreCoordinator {
return alreadyInitializedInstance
}
else {
self._persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.persistentStoreURL
var error: NSError? = nil
var failureReason = "There was an error creating or loading the application's saved data."
if self._persistentStoreCoordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil, error: &error) == nil {
self._persistentStoreCoordinator = nil
// Report any error we got.
let dict = NSMutableDictionary()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason
dict[NSUnderlyingErrorKey] = error
error = NSError.errorWithDomain(self.errorDomain, code: 9999, userInfo: dict)
// TODO: Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog("Unresolved error \(error), \(error!.userInfo)")
abort()
}
}
return self._persistentStoreCoordinator
}
}
private var _persistentStoreCoordinator: NSPersistentStoreCoordinator?
public var mainQueueContext: NSManagedObjectContext? {
return resultSynchronizedWithLock(lock) {
if let alreadyInitializedInstance = self._mainQueueContext {
return alreadyInitializedInstance
}
else if let coordinator = self.persistentStoreCoordinator {
self._mainQueueContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
self._mainQueueContext?.persistentStoreCoordinator = coordinator
}
return self._mainQueueContext
}
}
private var _mainQueueContext: NSManagedObjectContext?
public var backgroundQueueContext: NSManagedObjectContext? {
return resultSynchronizedWithLock(lock) {
if let alreadyInitializedInstance = self._backgroundQueueContext {
return alreadyInitializedInstance
}
else if let coordinator = self.persistentStoreCoordinator {
self._backgroundQueueContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
self._backgroundQueueContext?.persistentStoreCoordinator = coordinator
}
return self._backgroundQueueContext
}
}
private var _backgroundQueueContext: NSManagedObjectContext?
// MARK: - Persistent store file management
func doesPersistentStoreFileExist() -> Bool {
if let path = persistentStoreURL.path {
return NSFileManager.defaultManager().fileExistsAtPath(path)
}
else {
return false
}
}
func deletePersistentStoreFile(error: NSErrorPointer) {
synchronizedWithLock(self.lock) {
if self.doesPersistentStoreFileExist() {
NSFileManager.defaultManager().removeItemAtURL(self.persistentStoreURL, error: error)
}
}
}
// MARK: Save propagation
func contextDidSaveBackgroundQueueContext(notification: NSNotification) {
synchronizedWithLock(lock) {
self.mainQueueContext!.performBlock {
if verboseCoreDataMergeLoggingEnabled {
log(.Verbose, "merging changes from background queue context to main queue context")
}
self.mainQueueContext!.mergeChangesFromContextDidSaveNotification(notification)
}
}
}
func contextDidSaveMainQueueContext(notification: NSNotification) {
synchronizedWithLock(lock) {
self.backgroundQueueContext!.performBlock {
if verboseCoreDataMergeLoggingEnabled {
log(.Verbose, "merging changes from main queue context to background queue context")
}
self.backgroundQueueContext!.mergeChangesFromContextDidSaveNotification(notification)
}
}
}
}
@DivineDominion
Copy link

Thanks for sharing! I have the feeling the Data Store/Stack grew in size tremendously compared to Objective-C

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment