I've had a similar problem a while ago and solved it like this:
@objc protocol CommonAttributes: NSFetchRequestResult { // this protocol lists all attributes my different entities have in common, I used the autogenerated classes for them
init(context: NSManagedObjectContext) // I needed this elsewhere
var date: Date? { get set }
// etc.
}
// the three entities that already have a date attribute can conform to this protocol like so:
extension MyFirstEntity: CommonAttributes { }
extension MySecondEntity: CommonAttributes { }
extension MyThirdEntity: CommonAttributes { }
// in whatever type performa the generic fetch:
func genericFetch<T: CommonAttributes>(usingCreator requestCreator: () -> NSFetchRequest<T>, andContainer container: NSPersistentContainer) {
let fetchRequest = requestCreator() as NSFetchRequest<T>
do {
let numRecords = try container.viewContext.count(for: request)
if numRecords == 0 {
// I just use a date in this example, so:
let newEntry = T(context: container.viewContext)
newEntry.date = Date()
try container.viewContext.save()
}
} catch {
print("Error")
}
// using the fetchMethod like this on, for example, MyFirstEntity:
genericFetch(usingCreator: MyFirstEntity.fetchRequest, andContainer: myPersistentContainer) // note that I pass MyFirstEntity.fetchRequest as a closure, I don't invoke it here!
Note that I rely on the static methods provided by the autogenerated classes for the entities here, if you use specific types you might need to adapt. I originally wanted to include the fetchRequest method in my CommonAttributes protocol, but that didn't work (it's been a while, so I assume that was due to the "Self requirements" problems for existentials; perhaps that's now possible to do slightly better with the new stuff since Swift 5.6). Then I'd have used the method signature
func genericFetch<T: CommonAttributes>(forType typeToFetch: T.Type, andContainer container: NSPersistentContainer)
// call like:
genericFetch(forType: MyFirstEntity.self, andContainer: myContainer)
If you want to avoid passing the fetchRequest() method as parameter and cannot include it in the protocol, you can also simply constrain T to your CommonAttributes and NSManagedObject, then do a soft cast to T:
func genericFetch<T: CommonAttributes & NSManagedObject>(forType typeToFetch: T.Type, andContainer container: NSPersistentContainer) {
let fetchR = T.fetchRequest() // this comes from NSManagedObject's definition and just gives an NSFetchRequest<NSFetchRequestResultType> object back
// ...
}
Since you just want to count the objects this might be easier, you can still use T(context:) to create new elements. I think this gives the correct count (99 % sure), but better double check that it doesn't give the summed count for all 3 entities...
.
If you want to do anything concretely with fetched objects from this, you have to cast them to T like this:
if let fetchedObjects = container.viewContext.fetch(fetchR) as? [T] {
// manipulate the attributes defined in `CommonAttributes`
}
Of course I am assuming that CommonAttributes includes all the attributes/properties you need to define a valid (non-constraint-violating) new object in all above, but that's a problem you have to address in any case for every possible generic code for this. You can work with several protocols for different things in the different entities and then do different things in different if let asThirdEntity = oneFetchedElementThatIsT as? SomeAttributes or even ... as? MyThirdEntity, I think you get the gist.