How to create generic convenience initializer in Swift?

I'm tackling with generics in Swift. I've got extension to NSManagedObject class and wanted to create initializer which is only available for classes which implements some protocol I defined. Now I've got something like below but this is not working and even not compiling. Could you help me make it working?



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 says: "Generic parameter 'Self' is not used in function signature".



Answers

As matt already explained, you cannot define an initializer which
is restricted to types implementing a protocol. Alternatively, you
could define a global function instead:



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

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


which is then used as



let obj = createInstance(Entity.self, context)


You can avoid the additional type parameter if you define the method
as



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


and use it as



let obj : Entity = createInstance(context)


or



let obj = createInstance(context) as Entity


where the type is now inferred from the context.



Answers

It seems to me that you are describing something like this:



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


In real life, I presume that makeANewThing would actually do something with its caller, but the point is that it can only be called by passing a caller that has adopted ThingMaker.



That is probably the best you can do in Swift 1. If you want to inject a method into only classes that adopt a certain protocol, then what you want is a protocol extension — but that is available only in Swift 2.



Answers

Okay, thanks for your comments on this thread. I realized with @matt comments that I cannot do what I thought because it is even not possible. I wanted to not create subclass to make it working and it was not possible with my understanding of the problem.



Finally I found another way to get entity name. It has pros and cons but I decided to use it for now. Then I created extension for NSManagedObject to make it work.



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)
}
}


And then



obj = Obj(context: ctx)