Skip to content

Instantly share code, notes, and snippets.

@choncou
Last active May 22, 2016 21:15
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 choncou/5e62348ec849a6813c123a3c6bb2e1e5 to your computer and use it in GitHub Desktop.
Save choncou/5e62348ec849a6813c123a3c6bb2e1e5 to your computer and use it in GitHub Desktop.
Useful CoreData Boilerplate
//
// CoreDataStack.swift
//
//
// Created by Fernando Rodríguez Romero on 21/02/16.
// Copyright © 2016 udacity.com. All rights reserved.
//
import CoreData
struct CoreDataStack {
// MARK: - Properties
private let model : NSManagedObjectModel
private let coordinator : NSPersistentStoreCoordinator
private let modelURL : NSURL
private let dbURL : NSURL
private let persistingContext : NSManagedObjectContext
private let backgroundContext : NSManagedObjectContext
let context : NSManagedObjectContext
// MARK: - Initializers
init?(modelName: String){
// Assumes the model is in the main bundle
guard let modelURL = NSBundle.mainBundle().URLForResource(modelName, withExtension: "momd") else {
print("Unable to find \(modelName)in the main bundle")
return nil}
self.modelURL = modelURL
// Try to create the model from the URL
guard let model = NSManagedObjectModel(contentsOfURL: modelURL) else{
print("unable to create a model from \(modelURL)")
return nil
}
self.model = model
// Create the store coordinator
coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
// Create a persistingContext (private queue) and a child one (main queue)
// create a context and add connect it to the coordinator
persistingContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
persistingContext.persistentStoreCoordinator = coordinator
context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
context.parentContext = persistingContext
// Create a background context child of main context
backgroundContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
backgroundContext.parentContext = context
// Add a SQLite store located in the documents folder
let fm = NSFileManager.defaultManager()
guard let docUrl = fm.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first else{
print("Unable to reach the documents folder")
return nil
}
self.dbURL = docUrl.URLByAppendingPathComponent("model.sqlite")
do{
try addStoreCoordinator(NSSQLiteStoreType, configuration: nil, storeURL: dbURL, options: nil)
}catch{
print("unable to add store at \(dbURL)")
}
}
// MARK: - Utils
func addStoreCoordinator(storeType: String,
configuration: String?,
storeURL: NSURL,
options : [NSObject : AnyObject]?) throws{
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: dbURL, options: nil)
}
}
// MARK: - Removing data
extension CoreDataStack {
func dropAllData() throws{
// delete all the objects in the db. This won't delete the files, it will
// just leave empty tables.
try coordinator.destroyPersistentStoreAtURL(dbURL, withType:NSSQLiteStoreType , options: nil)
try addStoreCoordinator(NSSQLiteStoreType, configuration: nil, storeURL: dbURL, options: nil)
}
}
// MARK: - Batch processing in the background
extension CoreDataStack{
typealias Batch=(workerContext: NSManagedObjectContext) -> ()
func performBackgroundBatchOperation(batch: Batch){
backgroundContext.performBlock(){
batch(workerContext: self.backgroundContext)
// Save it to the parent context, so normal saving
// can work
do{
try self.backgroundContext.save()
}catch{
fatalError("Error while saving backgroundContext: \(error)")
}
}
}
}
// MARK: - Save
extension CoreDataStack {
func save() {
// We call this synchronously, but it's a very fast
// operation (it doesn't hit the disk). We need to know
// when it ends so we can call the next save (on the persisting
// context). This last one might take some time and is done
// in a background queue
context.performBlockAndWait(){
if self.context.hasChanges{
do{
try self.context.save()
}catch{
fatalError("Error while saving main context: \(error)")
}
// now we save in the background
self.persistingContext.performBlock(){
do{
try self.persistingContext.save()
}catch{
fatalError("Error while saving persisting context: \(error)")
}
}
}
}
}
func autoSave(delayInSeconds : Int){
if delayInSeconds > 0 {
print("Autosaving")
save()
let delayInNanoSeconds = UInt64(delayInSeconds) * NSEC_PER_SEC
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delayInNanoSeconds))
dispatch_after(time, dispatch_get_main_queue(), {
self.autoSave(delayInSeconds)
})
}
}
}
//
// CoreDataTableViewController.swift
//
//
// Created by Fernando Rodríguez Romero on 22/02/16.
// Copyright © 2016 udacity.com. All rights reserved.
//
import UIKit
import CoreData
class CoreDataTableViewController: UITableViewController {
// MARK: - Properties
var fetchedResultsController : NSFetchedResultsController?{
didSet{
// Whenever the frc changes, we execute the search and
// reload the table
fetchedResultsController?.delegate = self
executeSearch()
tableView.reloadData()
}
}
init(fetchedResultsController fc : NSFetchedResultsController,
style : UITableViewStyle = .Plain){
fetchedResultsController = fc
super.init(style: style)
}
// Do not worry about this initializer. I has to be implemented
// because of the way Swift interfaces with an Objective C
// protocol called NSArchiving. It's not relevant.
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
// MARK: - Subclass responsability
extension CoreDataTableViewController{
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
fatalError("This method MUST be implemented by a subclass of CoreDataTableViewController")
}
}
// MARK: - Table Data Source
extension CoreDataTableViewController{
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let fc = fetchedResultsController{
return (fc.sections?.count)!;
}else{
return 0
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let fc = fetchedResultsController{
return fc.sections![section].numberOfObjects;
}else{
return 0
}
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let fc = fetchedResultsController{
return fc.sections![section].name;
}else{
return nil
}
}
override func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
if let fc = fetchedResultsController{
return fc.sectionForSectionIndexTitle(title, atIndex: index)
}else{
return 0
}
}
override func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
if let fc = fetchedResultsController{
return fc.sectionIndexTitles
}else{
return nil
}
}
}
// MARK: - Fetches
extension CoreDataTableViewController{
func executeSearch(){
if let fc = fetchedResultsController{
do{
try fc.performFetch()
}catch let e as NSError{
print("Error while trying to perform a search: \n\(e)\n\(fetchedResultsController)")
}
}
}
}
// MARK: - Delegate
extension CoreDataTableViewController: NSFetchedResultsControllerDelegate{
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController,
didChangeSection sectionInfo: NSFetchedResultsSectionInfo,
atIndex sectionIndex: Int,
forChangeType type: NSFetchedResultsChangeType) {
let set = NSIndexSet(index: sectionIndex)
switch (type){
case .Insert:
tableView.insertSections(set, withRowAnimation: .Fade)
case .Delete:
tableView.deleteSections(set, withRowAnimation: .Fade)
default:
// irrelevant in our case
break
}
}
func controller(controller: NSFetchedResultsController,
didChangeObject anObject: AnyObject,
atIndexPath indexPath: NSIndexPath?,
forChangeType type: NSFetchedResultsChangeType,
newIndexPath: NSIndexPath?) {
switch(type){
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Update:
tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Move:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment