如何在 Swift 4 中为 NSFetchedResultsController 编写扩展

How to write an extension for NSFetchedResultsController in Swift 4

我正在尝试为 Swift 中的 NSFetchedResultsController class 编写一个简单的扩展 4.

这是我的第一次尝试 - 在 Swift 3:

public extension NSFetchedResultsController
{
    public func sectionCount() -> Int
    {
        if self.sections == nil
        {
            return 0
        }

        return self.sections!.count
    }
}

但是我在 Xcode 9 beta 2 和 Swift 4:

中得到这个编译错误

Extension of a generic Objective-C class cannot access the class's generic parameters at runtime

我试过其他变体都无济于事。请注意,我可以创建一个扩展绑定到特定类型的 NSManagedObject 匹配 resultType;但这有一个缺点,我需要为 NSFetchedResultsController.

使用的每个托管对象类型创建一个扩展

最新的 Swift 4 个文档似乎没有很好地解释这一点。

我想我找到了这个问题的答案。 该功能需要对 Objective-C.

可用

@objc 添加到 func 应该可以消除编译错误。至少对我有用!

我们最终得到了子类 (Swift 4)。背后的想法 - 将类型作为参数传递。其余设置操作 NSManagedObject 类型以解决编译错误。

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 { [=10=] as? T }
      return result
   }
}

扩展:

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
    }

}

用法:

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 不好,合成是更好的方法。这是我对此的看法:

public protocol FetchedDataProtocol {
    associatedtype T: NSFetchRequestResult
    var controller: NSFetchedResultsController<T> { get }
    subscript(_ indexPath: IndexPath) -> T { get }
}

这样做可以避免转换和映射:

public extension FetchedDataProtocol {
    subscript(_ indexPath: IndexPath) -> T {
        return controller.object(at: indexPath)
    }
}

然后你将它用作你自己的 class 有下标(和任何你想要的):

public struct MainFetchedData<T: NSFetchRequestResult>: FetchedDataProtocol {
    public let controller: NSFetchedResultsController<T>
    public init(_ controller: NSFetchedResultsController<T>) {
        self.controller = controller
    }
}