如何在Swift中创建通用的便利初始化器?

9
我正在处理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'”。

1
由于你没有采用Nameable,因此不清楚你在这里尝试做什么。它似乎没有任何目的。 此外,如何解决这个普遍性问题?此外,Self有意义,因此你不应该误用它作为占位符名称。 - matt
我有其他的 NSManagedObject 子类,想让它们能够调用 let obj = MyClass(context: context),但前提是它们必须实现 Nameable。我发现这个声明是错误的,应该更像这样:extension NSManagedObject where NSManagedObject: Nameable,但这种方式也不起作用。 - Tomasz Szulc
1
但是你不能阻止一个实例基于该实例所属的类调用let obj = MyClass(context:context)!这在任何语言中都没有意义。如果有一个初始化程序MyClass(context:context),_任何人_都可以调用它(如果他们能看到它)。这就是面向对象编程的本质,如果你明白我的意思的话。我也看不出为什么你会想这样做。 - matt
1
如果你想将一个方法注入到只采用某个协议的类中,那么你需要的是一个协议扩展(在Swift 2.0中)。 - matt
3个回答

12
正如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

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


这似乎是最接近我想要的,但仍然需要在方法的参数中传递类型,而我想避免这种情况。正如我在答案中所描述的那样,我对问题的理解是错误的 ;) 无论如何,感谢您提供这个片段! - Tomasz Szulc
1
@TomaszSzulc:请查看更新(但我不确定哪个版本更好) - Martin R
我很喜欢这个解决方案 ;) 谢谢! - Tomasz Szulc
2
我喜欢这个技巧,将占位符作为返回类型,以避免将其作为参数传递实例,就像我在我的答案中所做的那样。我需要更经常地使用这个技巧! :) - matt

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 2。

然而,在您的实际示例中,您将遇到进一步的困难:由于我在这里解释的原因,您将无法检索到被定义为Nameable类型的某个静态“name”属性:https://dev59.com/B4vda4cB1Zd3GeqPgPIg#30824093。再次提醒,解决方案是升级到Swift 2。 - matt

0

好的,感谢您对此主题的评论。在@matt的评论下,我意识到我不能做我想做的事情,因为它甚至不可能。我想不创建子类就使它工作,但是根据我对问题的理解,这是不可能的。

最后,我找到了另一种获取实体名称的方法。它有利有弊,但我决定暂时使用它。然后我创建了一个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)

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