Swift - 核心数据 - codegen - 使扩展符合协议吗?

时间:2016-11-30 12:45:48

标签: ios objective-c swift xcode core-data

我在Xcode 8的核心数据编辑器中找到有关新codegen功能的文档有点稀疏。

这是"在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 个答案:

答案 0 :(得分:2)

Codegen类可以符合协议。并且,Swift协议可以提供任何函数的默认实现。

所以,至少在理论上,实现你在Swift中所做的比在Objective-C中更简单。但是,正确使用语法可能有点棘手。

主要问题如下:

  • 使用@objc作为协议声明的前言,以便它可以与CoreData和NSManagedObject一起使用
  • 函数的实现不包含在协议本身中,而是包含在扩展
  • 使用where子句约束扩展,仅应用于NSManageObject的子类(应该如此)。并且,通过这样做,扩展接收NSManageObject的功能
  • 最后,与往常一样,为了不修改Xcode codegen(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 {

}

ASIDE

请注意,由于某种原因,编译器会拒绝更简单的行:

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

这个更简单的行会产生以下错误:Cannot convert value of type 'NSFetchRequest<NSFetchRequestResult>' to specified type 'NSFetchRequest<Self>'。但是,出于某种原因,上述解决方法并没有。

非常欢迎任何关于这种情况发生的想法。