user6902806
user6902806

Reputation: 280

swift convenience init and generic class

I have a problem creating a convenience init method that then calls a designated init on a class with generic type parameters. Here is the swift 3.1 XCode Version 8.3.2 (8E2002) playground

protocol A {
    var items: [String] { get set }
    func doSomething()
}

struct Section : A {
    var items: [String] = []

    func doSomething() {
        print("doSomething")
        items.forEach { print($0) }
    }
}

class DataSource<T: A> {
    var sections: [T]

    init(sections: [T]) {
        self.sections = sections
    }

    func process() {
        sections.forEach { $0.doSomething() }
    }

    convenience init() {
        var section = Section()
        section.items.append("Goodbye")
        section.items.append("Swift")

        self.init(sections: [section])
    }
}

/*: Client */
var section = Section()
section.items.append("Hello")
section.items.append("Swift")

let ds = DataSource(sections: [section])
ds.process()

If no convenience init exists, then the code beneath the /*: Client */ section compiles and executes without issue. If I add in the convenience init I get the following compilation error:

cannot convert value of type '[Section]' to expected argument type '[_]'
        self.init(sections: [section])

I wouldn't think that this would be an issue since in the convenience init I am creating a Section struct which implements the protocol A which satisfies the generic constraint on the DataSource class. The convenience init is performing the same operations as the client code, yet it is unable to convert a [Section] into a [A]. Is this an initialization sequencing issue?

Upvotes: 5

Views: 2571

Answers (1)

Hamish
Hamish

Reputation: 80931

Generic placeholders are satisfied at the usage of the given generic type – therefore inside your convenience init, you cannot assume that T is a Section. It's an arbitrary concrete type that conforms to A.

For example, it would be perfectly legal for the caller to define a

struct SomeOtherSection : A {...}

and then call your convenience initialiser with T being SomeOtherSection.

The solution in this case is simple, you can just add your convenience initialiser in an extension of DataSource, where T is constrained to being Section – therefore allowing you to
call init(sections:) with a [Section]:

extension DataSource where T == Section {

    convenience init() {
        var section = Section()
        section.items.append("Goodbye")
        section.items.append("Swift")

        self.init(sections: [section])
    }
}

// ...

// compiler will infer that T == Section here.
let ds = DataSource()

Upvotes: 10

Related Questions