Last active
May 22, 2016 21:15
-
-
Save choncou/5e62348ec849a6813c123a3c6bb2e1e5 to your computer and use it in GitHub Desktop.
Useful CoreData Boilerplate
This file contains 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
// | |
// 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) | |
}) | |
} | |
} | |
} |
This file contains 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
// | |
// 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