I have a class in Swift that wraps multiple NSFetchedResultsControllers, becomes their delegate, and converts the IndexPaths before returning to it's own delegate. This class can take NSFetchedResultsControllers returning different entities as long as they conform to the same Protocol. When upgrading to Swift 3 I can't get this same functionality to compile.
Let say I want to wrap two NSFetchedResultsControllers returning two different Entity types to show in a single tableView. Both CoreData entities conform to the following protocol
protocol ManagedObjectDisplayType : NSFetchRequestResult {
var id:String { get }
func friendlyName() -> String
}
The problem is that now NSFetchedResultsControllers are generic, there is no concrete type of NSFetchedResultsController that I can pass in to my Wrapper class since the two controllers are of different types.
For example:
let entity1Request = NSFetchRequest<Entity1>(entityName: entityName)
let entity1Frc = NSFetchedResultsController<ManagedObjectDisplayType>(fetchRequest: entity1Request, managedObjectContext:mainManagedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
let entity2Request = NSFetchRequest<Entity2>(entityName: entityName)
let entity2Frc = NSFetchedResultsController<ManagedObjectDisplayType>(fetchRequest: entity2Request, managedObjectContext:mainManagedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
When I do this I get the following error: "Using 'ManagedObjectDisplayType' as a concrete type conforming to protocol 'NSFetchRequestResult' is not supported" which makes total sense.
But I'm not sure of another way to do what I'm trying to do.
This looks like a job for...
There has been lots written about type erasure in Swift, but this is an interesting situation. I'm assuming you're having problems creating an object that can hold multiple NSFetchedResultsController
for your different types. The problem is that Swift (in its current version) needs a concrete type to correctly allocate the needed memory. The standard solution to this in the standard library and elsewhere is to create a box that hides the underlying type. In this case, the AnyFetchedResultsController
class below effectively erases the concrete type (an NSFetchedResultsController<T> where T: ManagedObjectDisplayType
) that it boxes.
class AnyFetchedResultsController: CustomDebugStringConvertible
{
var descImpl: () -> String
var performImpl: () throws -> ()
init<T>(_ controller: NSFetchedResultsController<T>) where T: ManagedObjectDisplayType {
descImpl = { controller.debugDescription }
performImpl = { try controller.performFetch() }
}
func performFetch() throws {
try performImpl()
}
var debugDescription: String {
return "wrapping \(descImpl())"
}
}
let entity1Request = NSFetchRequest<Entity1>(entityName: "Foobar")
let entity1Frc = NSFetchedResultsController<Entity1>(fetchRequest: entity1Request, managedObjectContext:mainManagedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
let entity2Request = NSFetchRequest<Entity2>(entityName: "Barfoo")
let entity2Frc = NSFetchedResultsController<Entity2>(fetchRequest: entity2Request, managedObjectContext:mainManagedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
let frcs: [AnyFetchedResultsController] = [AnyFetchedResultsController(entity1Frc), AnyFetchedResultsController(entity2Frc)]
Now that you have a way of storing these fetched results controllers, you'll need to flesh out the AnyFetchedResultsController
class with any additional methods you need to call on the underlying NSFetchedResultsController
.
I hope that makes sense. Please come back if you have further questions!
The original signature for AnyFetchedResultsController.init
:
init<T, U: NSFetchedResultsController<T>>(_ controller: U, _ managedObjectType: T? = nil) where T: ManagedObjectDisplayType
was pretty complicated, and had a dummy managedObjectType parameter which seemed to be necessary to fix some compiler errors. However, I just discovered that the more simple:
init<T>(_ controller: NSFetchedResultsController<T>) where T: ManagedObjectDisplayType
(also above) seems to work just as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With