Swift - Core Data - codegen - 让扩展符合协议?

4

我发现Xcode 8中Core Data编辑器中的新代码生成功能文档有些不够充分。

这是一个“如果用Objective-C语言,我会......”的问题。

我正在尝试声明一个协议,其中包含两个方法:

@property (strong, readonly) NSNumber *serverId;

+ (instancetype)objectWithServerId:(NSNumber*)serverId inContext:(NSManagedObjectContext*)moc;

在Objective-C中,我会使用mogenerator声明生成的基类应为"MyBaseClass"。
在该基类中,我可以实现该类方法一次。在Core Data编辑器中,我只需确保我的实体具有该属性。在“可读性强”的文件中,我将声明它符合该协议,并且因为它继承自该基类(基本上是抽象的),它可以调用上面列出的那个类方法。
我认为在强类型语言中,这可能不可能。我已经使其工作,但我创建的每个子类(使用Xcode生成的扩展)都必须实现此方法,而我更喜欢将该方法编写一次作为通用方法。
在Swift中,我将该属性添加到实体中(没有父类,因此它是NSManagedObject的子类),并执行以下操作:
protocol RemoteObjectProtocol {

    var serverId: Int64 {get}

    static func object<T: NSManagedObject>(WithServerId serverId: Int64, context: NSManagedObjectContext!) -> T?
}


import CoreData


@objc(TestObject)
class TestObject: NSManagedObject {


}


extension TestObject: RemoteObjectProtocol {
    // by nature of it being in the generated core data model, we just need to declare its conformance.

    static func object<T: NSManagedObject>(WithServerId serverId: Int64, context: NSManagedObjectContext!) -> T? {

        // IF YOU FIND A BETTER WAY TO SOLVE THIS, PLEASE DO!
        // I would have loved to use a baseclass RemoteObject: NSManagedObject, where you can just implement this
        // Method.  But there was no way for it to play nicely with the CoreData Editor

        // in addition, the convenience method generated by Xcode .fetchRequest() only seems to work outside of this extension.  
        // Extensions can't make use of other extensions
        // So we create the fetch request 'by hand'

        let fetchRequest = NSFetchRequest<TestObject>(entityName: "TestObject")
        fetchRequest.predicate = NSPredicate(format: "serverId == %i", serverId)
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "serverId", ascending: true)]
        fetchRequest.fetchLimit = 1

        do {
            let fetchedObjects = try context.fetch(fetchRequest)

            return fetchedObjects.first as! T?

        } catch {
            log.warning("Failed to fetch object with serverId:\(serverId) error:\(error)")
        }

        return nil
    }
}
1个回答

3

代码生成类可以遵守协议。而且,在 Swift 中,协议可以提供任何函数的默认实现。

因此,至少理论上来说,用 Swift 实现你想要的功能应该比 Objective-C 更简单。但是,正确书写语法可能会有点棘手。

主要问题如下:

  • 在协议声明之前加上 @objc,这样它就能与 CoreData 和 NSManagedObject 一起使用
  • 函数的实现不包含在协议本身中,而是包含在扩展中
  • 使用 where 子句限制扩展仅适用于 NSManageObject 的子类(应该如此)。通过这样做,扩展将接收 NSManageObject 的功能
  • 最后,为了不修改 Xcode 代码生成器(位于 Derived Data 文件夹中),请像往常一样,在每个 NSManagedObject 子类的扩展中遵守协议。

因此,以下代码应该定义协议:

import CoreData

@objc protocol RemoteObject {

    var serverId: Int64 { get }
}

extension RemoteObject where Self: NSManagedObject {

    static func objectWithServerId(_ serverId: Int64, context: NSManagedObjectContext) -> Self? {

        let fetchRequest: NSFetchRequest<Self> = NSFetchRequest(entityName: Self.entity().name!)

        let predicate = NSPredicate(format: "serverId == %d", serverId)
        let descriptor = NSSortDescriptor(key: #keyPath(RemoteObject.serverId), ascending: true)
        fetchRequest.predicate = predicate
        fetchRequest.sortDescriptors = [descriptor]
        fetchRequest.fetchLimit = 1

        do {
            let results = try context.fetch(fetchRequest)
            return results.first
        } catch {
            print("Failed to fetch object with serverId:\(serverId) error:\(error)")
        }
        return nil
    }
}

正如你所注意到的,每个实体已经拥有serverId属性。因此,你只需要声明它符合协议:

extension MyCoreDataEntity: RemoteObject {

}

附注

请注意,由于某些原因,编译器会拒绝较为简单的语句:

let fetchRequest: NSFetchRequest<Self> = Self.fetchRequest()

较简单的代码行生成以下错误: 无法将类型为 'NSFetchRequest<NSFetchRequestResult>' 的值转换为指定类型 'NSFetchRequest<Self>'。 但是,由于某种原因,上述解决方法不会出现这个问题。

欢迎提出任何关于为什么会发生这种情况的想法。


这很棒。谢谢!我今天学到了一些东西。我认为不会发生的原因是因为.fetchRequest()方法是在类扩展中定义的(由Xcode的代码生成器创建),所以这两个扩展程序不“知道”彼此??然而,我不确定理论上的原因。在Objective-C中,您知道哪个类别在哪个类别之前导入,所以我不知道Swift。 - horseshoe7
@horseshoe7,你可能有所发现,尽管我认为entity()在同一位置被定义。当编译器从NSFetchRequest(entityName:)接收获取请求时,它似乎不会对let语句进行类型检查。在这种情况下,它对实体没有任何了解,因为初始化程序仅采用字符串。如果您明确地将其他语句强制转换为as! NSFetchRequest<Self>,它也可以正常工作。但是,通常不需要这样做。这总是有效的:let fetchRequest: NSFetchRequest<MyEntity> = MyEntity.fetchRequest()。也许,这只是一个怪癖,不重要。 - salo.dm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接