Created
September 28, 2014 14:51
-
-
Save kristopherjohnson/6083bfc1216cb7ae3761 to your computer and use it in GitHub Desktop.
Swift: Core Data persistence stack
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for sharing! I have the feeling the Data Store/Stack grew in size tremendously compared to Objective-C