Skip to content

Instantly share code, notes, and snippets.

@Sorix
Last active January 13, 2018 12:46
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 Sorix/8201ca10d0d0340d49acdc318aafc392 to your computer and use it in GitHub Desktop.
Save Sorix/8201ca10d0d0340d49acdc318aafc392 to your computer and use it in GitHub Desktop.
NSFetchedResultsController for single object
import CoreData
protocol SingleFetchedResultsControllerDelegate: class {
func controller(didChange anObject: NSFetchRequestResult, for type: SingleFetchedResultsChangeType)
func controller(error: SingleFetchedResultsControllerError)
}
extension SingleFetchedResultsControllerDelegate {
func controller(_ controller: SingleFetchedResultsController<NSFetchRequestResult>, error: SingleFetchedResultsControllerError) { }
}
enum SingleFetchedResultsControllerError: Error {
case multipleResultsFetched([NSFetchRequestResult])
}
enum SingleFetchedResultsChangeType {
case inserted
case updated
case deleted
fileprivate init?(fromNSObjectsKey key: String) {
switch key {
case NSInsertedObjectsKey: self = .inserted
case NSUpdatedObjectsKey: self = .updated
case NSDeletedObjectsKey: self = .deleted
default: return nil
}
}
fileprivate var key: String {
switch self {
case .inserted: return NSInsertedObjectsKey
case .updated: return NSUpdatedObjectsKey
case .deleted: return NSDeletedObjectsKey
}
}
}
class SingleFetchedResultsController<ResultType> where ResultType: NSFetchRequestResult {
let fetchRequest: NSFetchRequest<ResultType>
let context: NSManagedObjectContext
weak var delegate: SingleFetchedResultsControllerDelegate? {
didSet {
if delegate != nil {
addObserverIfNeeded()
}
}
}
private(set) var object: ResultType?
private var observerIsActive = false
init(managedObjectContext: NSManagedObjectContext, fetchRequest: NSFetchRequest<ResultType>) {
self.context = managedObjectContext
self.fetchRequest = fetchRequest
}
func performFetch() throws {
let results = try context.fetch(fetchRequest)
object = results.first
if results.count > 1 {
throw SingleFetchedResultsControllerError.multipleResultsFetched(results)
}
}
private func addObserverIfNeeded() {
if observerIsActive { return }
var observer: NSObjectProtocol?
observer = NotificationCenter.default.addObserver(forName: .NSManagedObjectContextDidSave, object: context, queue: .main) { [weak self] (notification) in
guard let me = self else {
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
return
}
me.updateObject(from: notification, changeType: .inserted)
me.updateObject(from: notification, changeType: .updated)
me.updateObject(from: notification, changeType: .deleted)
}
observerIsActive = true
}
private func updateObject(from notification: Notification, changeType: SingleFetchedResultsChangeType) {
guard let modifiedObjects = notification.userInfo?[changeType.key] as? Set<NSManagedObject> else { return }
let objectsWithCorrectType = modifiedObjects.filter({ $0 is ResultType })
let predicate = fetchRequest.predicate ?? NSPredicate(value: true)
let predicateFilteredObjects = NSSet(set: objectsWithCorrectType).filtered(using: predicate)
guard let updatedObject = predicateFilteredObjects.first as? ResultType else { return }
if predicateFilteredObjects.count > 1 {
guard let multipleObjects = Array(predicateFilteredObjects) as? [ResultType] else {
assertionFailure("Programmatic error")
return
}
delegate?.controller(error: SingleFetchedResultsControllerError.multipleResultsFetched(multipleObjects))
} else {
self.object = updatedObject
delegate?.controller(didChange: updatedObject, for: changeType)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment