First of all, sorry about the post length but I am very new to iOS and SwiftUI development and I don't want to miss any details. I did some small projects with Kotlin on Android and Flutter, so I had some experience in app development.
Context
I trying to create a simple app that persists the user data on CoreData and I trying to follow MVVM architecture to develop the app. I was inspired by the following post on Medium. And I have the following files:
- DataSource.swift: Class that abstracts the initialization of
NSPersistentContainer
. - Entity.swift: Protocol for CoreData entity class standardization.
- ProductEntity.swift: Particular CoreData class definition that conforms Entity protocol.
- Model.swift: Class with Entity generic that abstracts the model instantiation and updating process.
- ProductModel.swift: Particular CoreData entity model definition that inherits Model<ProductEntity> (where exception raises).
The exception
I got an exception initializing the ProductsModel
class (ProductsModel.swift, check it below) and I don't have any idea about where are the error source and its reason.
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'An instance of NSFetchedResultsController requires a fetch request with sort descriptors'
I hope you can give me some clues! :)
The code
DataSource.swift:
import Foundation
import CoreData
let defaultDatabase = "DB"
class DataSource {
static let shared = DataSource()
public let container: NSPersistentContainer
init(dbName: String = defaultDatabase) {
container = NSPersistentContainer(name: dbName)
container.loadPersistentStores { (_, err) in
if let error = err as NSError? {
print("NSError \(error) - \(error.userInfo)")
return
}
}
}
func save() {
do {
print("Saving context")
try self.container.viewContext.save()
print("Successfully saved context")
} catch {
print("ERROR: \(error as NSObject)")
}
}
}
Entity.swift:
import CoreData
protocol Entity: NSFetchRequestResult {
associatedtype CurrentEntity: NSManagedObject
static var name: String { get }
}
ProductEntity.swift:
import os
import CoreData
@objc(ProductEntity)
public class ProductEntity: NSManagedObject, Entity {
typealias CurrentEntity = ProductEntity
static let name: String = "Product"
}
extension ProductEntity : Identifiable {
public var ID: String {
self.objectID.uriRepresentation().absoluteString
}
}
extension ProductEntity {
@NSManaged public var desc: String?
@NSManaged public var name: String
@NSManaged public var price: Double
@NSManaged public var rations: Int16
@NSManaged public var shoppingList: NSSet?
}
Model.swift:
import Combine
import CoreData
import os
class Model<T: Entity>: NSObject, ObservableObject, NSFetchedResultsControllerDelegate {
var records = CurrentValueSubject<[T.CurrentEntity], Never>([])
private let controller: NSFetchedResultsController<T.CurrentEntity>
override init() {
controller = NSFetchedResultsController(
fetchRequest: NSFetchRequest<T.CurrentEntity>(entityName: T.name),
managedObjectContext: DataSource.shared.container.viewContext,
sectionNameKeyPath: nil, cacheName: nil
)
super.init()
controller.delegate = self
do {
try controller.performFetch()
records.value = (controller.fetchedObjects ?? []) as [T.CurrentEntity]
} catch {
NSLog("Error: could not fetch objects")
}
}
public func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
guard let records = controller.fetchedObjects as? [T.CurrentEntity] else { return }
self.records.value = records
}
public func save() {
DataSource.shared.save()
}
}
ProductModel.swift:
import os
class ProductsModel: Model<ProductEntity> {
static let shared: ProductsModel = ProductsModel() // <-- This line raise the exception
}