lucasmenendez
lucasmenendez

Reputation: 116

Problems creating a generic MVVM connector to CoreData

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:

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
}

Upvotes: 0

Views: 376

Answers (1)

Turtleeeeee
Turtleeeeee

Reputation: 179

NSFetchedResultsController is a tool which could manage your search results from Core Data. It needs at least one sort descriptor to maintain the list of your fetch request. So you should improve the NSFetchRequest you defined like below to resolve the exception.

let req = NSFetchRequest<T.CurrentEntity>(entityName: T.name)
req.sortDescriptors = [NSSortDescriptor(key: "someKeyForSort", ascending: true)]

In addition, "someKeyForSort" is the name of a property of T.CurrentEntity. If ProductEntity is the type, "name" could be the key assuming you want NSFetchedResultsController to maintain the fetched results sorted by name in ascending order.

Upvotes: 1

Related Questions