Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 475
user6902806 Avatar asked May 19 '17 09:05

user6902806


People also ask

What is Swift convenience init?

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.

What is generic class in Swift?

Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.

Can you override a convenience init?

It's not illogical to override a convenience initializer. Instead, it's unsafe to override a convenience initializer — in the context of all the other rules about designated and convenience initializers, and the two-passes of initializations, and order in which initialization occurs.


1 Answers

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()
like image 91
Hamish Avatar answered Nov 10 '22 10:11

Hamish