The Core Data stack in Swift
Whenever Apple releases a new version of Xcode one of the first things that I do is look at the default templates and see if there are any new or interesting things.
This year, with the release of Swift, there are some pretty radical changes. Yet the Core Data stack initialization code is still the same.
There is nothing wrong with the default template code but there isn’t really anything right about it either. It is far better than it once was but it is still overly verbose and hard to follow.
Therefore, I present my Swift Core Data stack code that I will be using as I grok this language.
saveContext()
func saveContext () {
var error: NSError? = nil
let moc = self.managedObjectContext
if moc == nil {
return
}
if !managedObjectContext.hasChanges {
return
}
if managedObjectContext.save(&error) {
return
}
println("Error saving context: \(error?.localizedDescription)\n\(error?.userInfo)")
abort()
}
The save method is always the first one that I attack. I strongly dislike having conditions inside of conditions inside of conditions. It is ugly and hard to follow. I much prefer the fail early, fail often design. In my version of the saveContext()
we do just that. Every opportunity to fail (or finish) is exercised individually and if it is a failure or finish condition, we return.
All of the logic is on the left side and easy to follow.
managedObjectContext
(Version 2)
Next up is the stack itself. Apple creates four variables when we really only use one. It is rare to use persistentStoreCoordinator
more than once in the life cycle of an application and even more rare to use managedObjectModel
more than once. So why have individual variables for them? This is even more wasteful when you realize that both are accessible from the managedObjectContext
. Therefore, I prefer to roll them into one, fairly easy to follow variable.
@lazy var managedObjectContext: NSManagedObjectContext = {
let modelURL = NSBundle.mainBundle().URLForResource("SwiftTestOne", withExtension: "momd")
let mom = NSManagedObjectModel(contentsOfURL: modelURL)
ZAssert(mom != nil, "Error initializing mom from: \(modelURL)")
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
let storeURL = (urls[urls.endIndex-1]).URLByAppendingPathComponent("SwiftTestOne.sqlite")
var error: NSError? = nil
var store = psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil, error: &error)
if (store == nil) {
println("Failed to load store")
}
ZAssert(store != nil, "Unresolved error \(error?.localizedDescription), \(error?.userInfo)\nAttempted to create store at \(storeURL)")
var managedObjectContext = NSManagedObjectContext()
managedObjectContext.persistentStoreCoordinator = psc
return managedObjectContext
}()
Apple’s sample code uses two variables, one that is calculated and then one “private” variable that actually holds the reference. Thanks to lots of feedback from other developers we have a more elegant solution. A lazily loaded var. This simplifies the code quite a bit.
Inside of the getter is where all of the code from the other functions exist. We first grab the URL for the model and then pass that URL into the NSManagedObjectModel
. We then test to make sure that the NSManagedObjectModel
initialized properly. Right now ZAssert is a global function that is tied to a constant Bool to determine how it reacts to failure. I am still playing with that part so it is not fixed in stone and will get its own blog post once I am happy with it. For now it looks like:
let DEBUG = true
func ZAssert(test: Bool, message: String) {
if (test) {
return
}
println(message)
if (!DEBUG) {
return
}
var exception = NSException()
exception.raise()
}
Note that we are creating mom
as a constant. We know this object is not going to change for us so we don’t need to create it as a variable. Having constant objects like this is a pretty cool feature of Swift.
Once we know the NSManagedObjectModel
has been initialized properly we can move on to our NSPersistentStoreCoordinator
. Creating the NSPersistentStoreCoordinator
is just a matter of passing in the initialized NSManagedObjectModel
that we just created. We create the NSPersistentStoreCoordinator
as another constant. Next we need to load a NSPersistentStore
into the NSPersistentStoreCoordinator
.
To do that we need to know where the store is going to be saved. We grab all of the possible locations for the documents directory and select the last one from the returned array. We can then append the filename of our store to the end of the NSURL
.
With a location we can now ask the NSPersistentStoreCoordinator
to add a store for that location. We call the method addPersistentStoreWithType
on the NSPersistentStoreCoordinator
and we get back either a NSPersistentStore
or nothing. If we get nothing back then that is a failure which we check for.
Finally after the NSPersistentStore
has been created we can create the actual NSManagedObjectContext
. We just initialize the NSManagedObjectContext
, hand it the NSPersistentStoreCoordinator
and return.
Wrap Up
This has changed about three times already and will probably change again. I am not crazy about how the storeURL
is being constructed and will probably clean that up as I go forward. I am certain my ZAssert
is going to change.
If you have any suggestions on how to improve, simplify this, please shoot me an email at the usual place. Hopefully we can get down to the infamous five lines of code soon. Right now we are sitting at about 8 (we started around 11)…