【问题标题】:Swift - Core Data - codegen - Make the extension conform to a protocol?Swift - 核心数据 - 代码生成 - 使扩展符合协议?
【发布时间】:2016-11-30 12:45:47
【问题描述】:

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

这是一个“在 Objective-C 中,我会……”之类的问题。

我正在尝试声明一个具有 2 种方法的协议:

@property (strong, readonly) NSNumber *serverId;

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

在 Objective-C 中,我会使用 mogenerator 来声明生成的基类应该是“MyBaseClass”。

在那个基类中,我可以实现一次该类方法。在核心数据编辑器中,我只需要确保我的实体具有该属性。在“人类可读”文件中,我会声明它符合那个协议,并且因为它继承自那个基类(基本上是抽象的),它可以调用上面列出的那个类方法。

我认为使用强类型,这可能是不可能的。我已经让它工作了,但是我创建的每个子类(使用 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
    }
}

【问题讨论】:

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


    【解决方案1】:

    Codegen 类可以符合协议。而且,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()
    

    更简单的行会产生以下错误:Cannot convert value of type 'NSFetchRequest&lt;NSFetchRequestResult&gt;' to specified type 'NSFetchRequest&lt;Self&gt;'。但是,由于某种原因,上述解决方法没有。

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

    【讨论】:

    • 这很漂亮。谢谢!我今天学到了一些东西。我认为不会发生这种情况的原因是因为 .fetchRequest() 方法是在类扩展中定义的(由 Xcode 的 codegen 创建),所以这两个扩展并不“知道”彼此?但是,我不确定其理论上的原因。在 Objective-C 中你知道哪个类别在哪个之前被导入,所以我不知道 Swift。
    • @horseshoe7,虽然我认为 entity() 是在同一个地方定义的,但您可能正在做某事。似乎编译器在收到来自NSFetchRequest(entityName:) 的提取请求时没有对 let 语句进行类型检查。在这种情况下,它不知道实体,因为初始化程序只接受一个字符串。而且,如果您明确地对另一条语句as! NSFetchRequest&lt;Self&gt; 进行类型转换,它也可以正常工作。但是,这通常是没有必要的。这总是有效的:let fetchRequest: NSFetchRequest&lt;MyEntity&gt; = MyEntity.fetchRequest() 也许,这只是一个怪癖,没关系。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-12
    • 2017-06-03
    • 1970-01-01
    相关资源
    最近更新 更多