I'm trying to write a simple extension to the NSFetchedResultsController
class in Swift 4.
Here's my first attempt - which worked in Swift 3:
public extension NSFetchedResultsController
{
public func sectionCount() -> Int
{
if self.sections == nil
{
return 0
}
return self.sections!.count
}
}
But I get this compile error in Xcode 9 beta 2 with Swift 4:
Extension of a generic Objective-C class cannot access the class's generic parameters at runtime
I've tried other variations to no avail. Note that I can create an extension bound to a specific type of NSManagedObject
matching the resultType
; but this has the disadvantage that I need to create an extension for every managed object type used with the NSFetchedResultsController
.
The latest Swift 4 docs don't seem to explain this well.
I think I found the answer to this one. The function needs to be available to Objective-C.
Adding @objc
to the func
should remove the compile error. At least it did for me!
We ended up with subclass (Swift 4). Idea behind - pass type as argument. Rest of setup operates NSManagedObject
type in order to work around compile errors.
public class FetchedResultsController<T: NSManagedObject>: NSFetchedResultsController<NSManagedObject> {
public convenience init(fetchRequest: NSFetchRequest<NSManagedObject>, context: NSManagedObjectContext, _: T.Type) {
self.init(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
}
public var numberOfSections: Int {
return sections?.count ?? 0
}
public var numberOfFetchedEntities: Int {
return fetchedObjects?.count ?? 0
}
public func entity(at indexPath: IndexPath) -> T {
if let value = object(at: indexPath) as? T {
return value
} else {
fatalError()
}
}
public func entity(at row: Int) -> T {
let ip = IndexPath(item: row, section: 0)
return entity(at: ip)
}
public var fetchedEntities: [T] {
let result = (fetchedObjects ?? []).compactMap { $0 as? T }
return result
}
}
Extensions:
extension Requests {
public static func all<T: NSManagedObject>(_: T.Type) -> NSFetchRequest<NSManagedObject> {
let request = NSFetchRequest<NSManagedObject>(entityName: T.entityName)
return request
}
public static func ordered<T: NSManagedObject>(by: String, ascending: Bool, _: T.Type) -> NSFetchRequest<NSManagedObject> {
let request = NSFetchRequest<NSManagedObject>(entityName: T.entityName)
request.sortDescriptors = [NSSortDescriptor(key: by, ascending: ascending)]
return request
}
}
extension NSManagedObject {
public static var entityName: String {
let className = NSStringFromClass(self) // As alternative can be used `self.description()` or `String(describing: self)`
let entityName = className.components(separatedBy: ".").last!
return entityName
}
public static var entityClassName: String {
let className = NSStringFromClass(self)
return className
}
}
Usage:
extension ParentChildRelationshipsMainController {
private func setupFetchedResultsController() -> FetchedResultsController<IssueList> {
let request = DB.Requests.ordered(by: #keyPath(IssueList.title), ascending: true, IssueList.self)
let result = FetchedResultsController(fetchRequest: request, context: PCDBStack.shared.mainContext, IssueList.self)
return result
}
}
extension ParentChildRelationshipsMainController: NSTableViewDataSource, NSTableViewDelegate {
func numberOfRows(in tableView: NSTableView) -> Int {
return frController.numberOfFetchedEntities
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let rowID = NSUserInterfaceItemIdentifier("rid:generic")
let item = frController.entity(at: row)
// ...
}
}
Subclass-ing is not good, composition is a better way to go. Here is my take on this:
public protocol FetchedDataProtocol {
associatedtype T: NSFetchRequestResult
var controller: NSFetchedResultsController<T> { get }
subscript(_ indexPath: IndexPath) -> T { get }
}
Doing it this way you avoid casting and mapping:
public extension FetchedDataProtocol {
subscript(_ indexPath: IndexPath) -> T {
return controller.object(at: indexPath)
}
}
And then you use it as your own class that has subscript (and whatever you want):
public struct MainFetchedData<T: NSFetchRequestResult>: FetchedDataProtocol {
public let controller: NSFetchedResultsController<T>
public init(_ controller: NSFetchedResultsController<T>) {
self.controller = controller
}
}
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