I've spent some time adding Core Data support to my Swift and Metal based reaction diffusion application. Core Data is a framework that allows me to persist different reaction diffusion configurations so that the user can save and reload different patterns.
There are several wrappers to Core Data that simplify its implementation including Alecrim and Sugar Record, but this blog post looks at the framework itself. My first ports of call were Jameson Quave's excellent tutorials and, of course, Ray Wenderlich.
When I first built my application, I'd omitted to check the 'Use Core Data' checkbox, so my first task was to create a new Swift project with that option checked and copy all the guts of the new AppDelegate into mine and ensuring the references in managedObjectModel and persistentStoreCoordinator were correct.
The next manual step is to create a data model that reflects the data I want to persist. This is done via File→ New → File and selecting Data Model under the Core Data options. Xcode now opens an editor where the model schema can be defined. Here's a screen grab of how my ReactionDiffusionEntity looks:
Once that's been created, I need a Swift class to mirror it. This is done from Editor → Create NSManaged Subclass and generates the following Swift:
class ReactionDiffusionEntity: NSManagedObject {
@NSManagedvar model: String
@NSManagedvar timestep: NSNumber
@NSManagedvar a0: NSNumber
@NSManagedvar a1: NSNumber
@NSManagedvar epsilon: NSNumber
@NSManagedvar delta: NSNumber
@NSManagedvar k1: NSNumber
@NSManagedvar k2: NSNumber
@NSManagedvar k3: NSNumber
@NSManagedvar f: NSNumber
@NSManagedvar k: NSNumber
@NSManagedvar du: NSNumber
@NSManagedvar dv: NSNumber
@NSManagedvar alpha: NSNumber
@NSManagedvar beta: NSNumber
@NSManagedvar gamma: NSNumber
@NSManagedvar imageData: NSData
}
I've added two new UIAlertAction instances to the drop down hamburger menu in the ReactionDiffusionEditor for saving and loading. These invoke saveModel() and loadModel() in my main view controller.
To save, I need to create an instance of ReactionDiffusionEntity in the Core Data context that copies the different parameter values from my existing value object. Inside the view controller's init(), I create a reference to both the application delegate and the context:
let appDelegate: AppDelegate
let managedObjectContext: NSManagedObjectContext
requiredinit(coder aDecoder: NSCoder)
{
appDelegate = UIApplication.sharedApplication().delegateasAppDelegate
managedObjectContext = appDelegate.managedObjectContext!
[...]
...and then in save I create the new entity based on my model, reactionDiffusionModel:
func saveModel()
{
var newEntity = ReactionDiffusionEntity.createInManagedObjectContext(managedObjectContext, model: reactionDiffusionModel.model.rawValue, reactionDiffusionStruct: reactionDiffusionModel.reactionDiffusionStruct, image: self.imageView.image!)
appDelegate.saveContext()
}
managedObjectContext and saveContext() both came for free when I created a new project with 'Use Core Data' checked.
The createInManagedObjectContext() method not only inserts a new object into the context, it also contains lines that map between my value object and the generated NSManagedObject:
newItem.timestep = reactionDiffusionStruct.timestep
newItem.a0 = reactionDiffusionStruct.a0
newItem.a1 = reactionDiffusionStruct.a1
newItem.epsilon = reactionDiffusionStruct.epsilon
newItem.delta = reactionDiffusionStruct.delta
[...]
You may also notice that I passed the view controller's image view's image into createInManagedObjectContext() - by using a little extension I wrote to resize images and UIImageJPEGRepresentation, I'm able to save a thumbnail of the reaction diffusion simulation as binary data:
newItem.imageData = UIImageJPEGRepresentation(image.resizeToBoundingSquare(boundingSquareSideLength: 160.0), 0.75)
To load, I use executeFetchRequest() to populate an array of ReactionDiffusionEntity:
func loadModel()
{
let fetchRequest = NSFetchRequest(entityName: "ReactionDiffusionEntity")
iflet fetchResults = managedObjectContext.executeFetchRequest(fetchRequest, error: nil) as? [ReactionDiffusionEntity]
{
// retrieved fetchResults.count records....
}
}
Now that fetchResults is populated, I pass that into my BrowseAndLoadController which is presented as a popover dialog. This contains not more than a UICollectionView to display all the entities inside fetchResults.
The item renderer creates a UIImage version of the entity's imageData using an observer on its reactionDiffusionEntity property:
var reactionDiffusionEntity: ReactionDiffusionEntity?
{
didSet
{
iflet _entity = reactionDiffusionEntity
{
label.text = _entity.model
let thumbnail = UIImage(data: _entity.imageDataasNSData)
imageView.image = thumbnail
}
}
}
When the user selects a model to load, I need to do the opposite of createInManagedObjectContext() - that is create a value object from my entity. I do this with a class function on ReactionDiffusionEntity which looks like a mirror image of createInManagedObjectContext():
classfunc createInstanceFromEntity(entity: ReactionDiffusionEntity) -> ReactionDiffusion!
{
var returnObject: ReactionDiffusion!
var model: ReactionDiffusionModels = ReactionDiffusionModels(rawValue: entity.model)!
switch model
{
case .BelousovZhabotinsky:
returnObject = BelousovZhabotinsky()
case .GrayScott:
returnObject = GrayScott()
case .FitzHughNagumo:
returnObject = FitzhughNagumo()
}
// populate numeric params...
returnObject.reactionDiffusionStruct.timestep = Float(entity.timestep)
returnObject.reactionDiffusionStruct.a0 = Float(entity.a0)
[...]
returnObject.reactionDiffusionStruct.beta = Float(entity.beta)
returnObject.reactionDiffusionStruct.gamma = Float(entity.gamma)
return returnObject
}
That's the basic saving and loading done. Because Xcode auto generates so much code, Core Data is pretty simple stuff. The next steps are to support deleting, editing and persisting state between sessions.
All the source code for this project is available at my Git Hub repository here.