Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create generic convenience initializer in Swift?

Tags:

generics

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".

like image 729
Tomasz Szulc Avatar asked Jun 17 '15 17:06

Tomasz Szulc


People also ask

How do I create a generic in Swift?

Swift Generic Function // create a generic function func displayData<T>(data: T){ ... } Here, We have created a generic function named displayData() . T used inside the angle bracket <> is called the type parameter.

What is convenience initializer in Swift?

Convenience initializers are secondary, supporting initializers for a class. You can define a convenience initializer to call a designated initializer from the same class as the convenience initializer with some of the designated initializer's parameters set to default values.

How do you designate a Failable initializer?

You write a failable initializer by placing a question mark after the init keyword ( init? ). Note: You cannot define a failable and a nonfailable initializer with the same parameter types and names. A failable initializer creates an optional value of the type it initializes.

How do you initialize a function in Swift?

An initializer is a special type of function that is used to create an object of a class or struct. In Swift, we use the init() method to create an initializer. For example, class Wall { ... // create an initializer init() { // perform initialization ... } }


2 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>(type : T.Type, context : NSManagedObjectContext) -> T where T: Nameable {
    let entity = NSEntityDescription.entity(forEntityName: T.entityName(), in: context)!
    return T(entity: entity, insertInto: 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>(context : NSManagedObjectContext) -> T where T: Nameable { ... }

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.

like image 104
Martin R Avatar answered Nov 27 '22 15:11

Martin R


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.

like image 42
matt Avatar answered Nov 27 '22 14:11

matt