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)
コメント