【问题标题】:How to create generic convenience initializer in Swift?如何在 Swift 中创建通用便利初始化程序?
【发布时间】:2018-09-05 23:04:46
【问题描述】:

我正在处理 Swift 中的泛型问题。我已经扩展了NSManagedObject 类,并希望创建仅可用于实现我定义的某些协议的类的初始化程序。现在我有类似下面的东西,但这不起作用,甚至无法编译。你能帮我让它工作吗?

public extension NSManagedObject {
    public convenience init<Self: Nameable>(context: NSManagedObjectContext)     {
        let entity = NSEntityDescription.entityForName(Self.entityName(), inManagedObjectContext: context)!
        self.init(entity: entity, insertIntoManagedObjectContext: context)
    }
}

public protocol Nameable {
    static func entityName() -> String
}

Xcode 说:“函数签名中未使用通用参数‘Self’”。

【问题讨论】:

  • 由于您没有使任何东西采用 Nameable,因此您甚至不清楚您要在这里做什么。似乎没有目的。 - 另外,如何解决这个泛型? - 此外,Self 有一个含义,因此您不应将其误用作占位符名称。
  • 我还有其他NSManagedObject 子类,并尝试让它们能够调用let obj = MyClass(context: context),但前提是它们实现了Nameable。我看到这是错误的声明,应该更像这样:extension NSManagedObject where NSManagedObject: Nameable 但它也不能这样工作。
  • 但是你不能根据实例是什么类来阻止实例调用let obj = MyClass(context:context)!这在任何语言中都毫无意义。如果有一个初始化器MyClass(context:context)任何人都可以调用它(如果他们能看到它)。这就是面向对象编程的本质,如果你明白我的意思的话。我也看不出你有什么理由想要这样做。
  • 如果您只想将一个方法注入到采用某种协议的类中,那么您需要的是协议扩展(在 Swift 2.0 中)。

标签: swift generics


【解决方案1】:

正如 matt 已经解释的那样,你不能定义一个初始化器 仅限于实现协议的类型。或者,你 可以定义一个全局函数:

public protocol Nameable {
    static func entityName() -> String
}

func createInstance<T : NSManagedObject>(type : T.Type, context : NSManagedObjectContext) -> T where T: Nameable {
    let entity = NSEntityDescription.entity(forEntityName: T.entityName(), in: context)!
    return T(entity: entity, insertInto: context)
}

然后用作

let obj = createInstance(Entity.self, context)

如果你定义了方法,可以避免额外的类型参数 作为

func createInstance<T : NSManagedObject>(context : NSManagedObjectContext) -> T where T: Nameable { ... }

并将其用作

let obj : Entity = createInstance(context)

let obj = createInstance(context) as Entity

现在从上下文中推断出类型。

【讨论】:

  • 这似乎最接近我想要的,但仍然需要在方法的参数中传递类型,我想避免这种情况。正如我的回答中所述,我对问题的理解是错误的;)无论如何感谢这个 sn-p!
  • @TomaszSzulc:查看更新(但我不确定哪个版本更好)
  • 我喜欢这个解决方案;)谢谢!
  • 我喜欢让占位符成为返回类型的技巧,以避免将它的实例作为参数传递,就像我在回答中所做的那样。我需要更频繁地使用这个技巧! :)
【解决方案2】:

在我看来,你在描述这样的事情:

class Thing {}

func makeANewThing<T:ThingMaker>(caller:T) -> Thing {
    let t = Thing()
    return t
}

protocol ThingMaker {
}

class Dog : ThingMaker {
}

class Cat { // not a ThingMaker
}

let t = makeANewThing(Dog()) // ok
let t2 = makeANewThing(Cat()) // illegal

在现实生活中,我认为 makeANewThing 实际上会一些事情,它的 caller,但关键是它只能通过传递一个采用 ThingMaker 的 caller 来调用.

这可能是你在 Swift 1 中能做的最好的事情。如果你只想将一个方法注入到采用某种协议的类中,那么你想要的是一个协议扩展——但这仅在 Swift 2 中可用。

【讨论】:

  • 但是,在您的实际示例中,您将遇到更多困难:您将无法检索类型为 Nameable 的静态 name 属性,原因我在这里解释:stackoverflow.com/a/30824093/341994 同样,解决方案是升级到 Swift 2。
【解决方案3】:

好的,感谢您在此线程上的 cmets。我通过@matt cmets 意识到我不能按照我的想法去做,因为这甚至是不可能的。我不想创建子类以使其正常工作,但以我对问题的理解是不可能的。

最后我找到了另一种获取实体名称的方法。它有利有弊,但我决定暂时使用它。然后我为NSManagedObject 创建了扩展以使其工作。

extension NSManagedObject {

    class func entityName() -> String {
        let fullClassName = NSStringFromClass(object_getClass(self))
        let nameComponents = split(fullClassName) { $0 == "." }
        return last(nameComponents)!
    }

    public convenience init(context: NSManagedObjectContext) {
        let name = self.dynamicType.entityName()
        let entity = NSEntityDescription.entityForName(name, inManagedObjectContext: context)!
        self.init(entity: entity, insertIntoManagedObjectContext: context)
    }
}

然后

obj = Obj(context: ctx)

【讨论】:

    猜你喜欢
    • 2019-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多