How to Call Initializer for Subclass of Generic Type

Unable to call initializer for subclass of generic type

With a lot of help from the answer originally posted by @rintaro, I was able to solve this problem, although there is still an oddity that I will post under a separate question.

As it turns out, @rintaro was absolutely correct in the need to initialize the instance of the generic type using the following syntax:

let result = (T.self as T.Type)(myRecord)

This works as long as the base class MyBaseClass declares this initializer with a required tag:

public class MyBaseClass {
public required init(_ record:MyRecordType) {
...
}
}

and the subclass MySubClass implements the matching initializer:

public class MySubClass : MyBaseClass {
public required init (_ record:MyRecordType) {
...
super.init(record)
}
}

Where things fail, however is when I actually have a 3-level class hierarchy and the initializer hierarchy throughs an override into the mix. To envision this, consider a set of class that represents nodes in a tree structure:

public class Node {
public init(_ record:MyRecordType) {
...
}
}

public class RootNode : Node {
override public init(_ record:MyRecordType) {
...
super.init(record)
}
public class func <T:RootNode>retrieveAll(success:(T) -> ()) {
// Retrieve all instances of root node subclass T, and invoke success callback with new T instance
}
}

public class ChildNode : Node {
public init(_ record:MyRecordType, parentNode:Node) {
...
super.init(record)
}
public class func <T:ChildNode>retrieveChildren(parent:Node, success:(T) -> ()) {
// Retrieve all child T instances of parent node, and invoke success callback with new T instance
{
}

The problem occurs in the implementation of the RootNode class's retrieveAll method. In order for it to work as described by @rintaro, I need the init in RootNode to be marked with the required keyword. But because it also overrides the initializer from Node, it also needs to have the override keyword. So I try to use both keywords in the declaration:

override required public init(_ record:MyRecordType) {
...
}

The Swift compiler accepts this, but when I use it to initialize an instance from within the retrieveAll method, it crashes with a BAD_ACCESS.

I was able to work around this problem by changing the method signature of the NodeClass just slightly so that its RootNode subclass doesn't need to override:

public class Node {
public init(record:MyRecordType) {
...
}
}
public class RootNode {
public required init(_ record:MyRecordType) {
...
super.init(record:record)
}
}

By doing this workaround, my subclasses of RootNode can properly implement the required initializer, and the retrieveAll method in RootNode can properly instantiate instances of those subclasses.

Inheriting initializer from generic class

This now works in Swift 3. My original example now compiles. There is no mention of this in the Swift 3 Language changes, so I can only assume that this was a bug.

override initializer from generic class

I am not sure what exactly goes wrong for you. When I run your code everything seems fine. Are you sure you're instantiating your instances correctly?

let checkItem = CheckItem("Check item data") { (string: String) -> String in
return string + "checkItem"
}

let labelItem = LabelItem<String, String, Int>("Label item") { (string: String) -> String in
return string + "labelItem"
}

let dataTableItem = DataTableItem<String, String, Int>("Data table item") { (string: String) -> String in
return string + "dataTableItem"
}

In LabelItem and DataTableItem you have a generic V which is not used anywhere and is not a parameter, so you need to be explicit with your types upon instantiation since you're not passing the V type in the init and the compiler can't infer the type. Thus <String, String, Int> or any other types that meet the constraints.

EDIT:

After looking into your project code (the project didn't run on my Xcode, I only copied relevant code into my project) I still see no problem - both initializers of CheckItem compile:

open class TableItem: NSObject {
public var title: String?
}

open class DataTableItem<T, U: Equatable & CustomStringConvertible, V: CustomStringConvertible>: TableItem{
let data: T
let getter: (T) -> U

public weak var host: TableItemHost?

public init(_ data: T, getter: @escaping (T) -> U) {
self.data = data
self.getter = getter
}
}

public protocol TableItemHost: class {}

open class CheckItem<T, U: Equatable & CustomStringConvertible>: DataTableItem<T,U,Bool> {
public init(_ data: T, host: TableItemHost, getter: @escaping (T) -> U) {
super.init(data, getter: getter)
self.host = host
}

public override init(_ data: T, getter: @escaping (T) -> U) {
super.init(data, getter: getter)
}
}

Creating instances:

let checkItem1 = CheckItem("Check item 1 ") { (string: String) -> String in
return string
}

class Host: TableItemHost {}
let host = Host()
let checkItem2 = CheckItem("Check item 2 ", host: host) { (string: String) -> String in
return string
}

print(checkItem1.data)
print(checkItem2.data)

Copy paste my code into a playground and see for yourself. Perhaps there is something other than the initializer causing the error.

For a test you can also try commenting out both initializers of CheckItem and instantiate it with the inherited initializer. That should work, because CheckItem will not have its own designated initializer anymore (https://stackoverflow.com/a/31459131/1433612)

Calling constructor of a generic type

There is no way to use the Java type system to enforce that a class hierarchy has a uniform signature for the constructors of its subclasses.

Consider:

public class ColorPencil extends Pencil
{
private Color color;

public ColorPencil(Color color)
{
super();
this.color=color;
}
}

This makes ColorPencil a valid T (it extends Item). However, there is no no-arg constructor for this type. Hence, T() is nonsensical.

To do what you want, you need to use reflection. You can't benefit from compile-time error checking.

How a generic class to conform a protocol (that have init in it) with constraint?

I don't know of a way to achieve this without making Graph final. If you do make it final, however, it will work fine as long as you remove required (required doesn't mean anything if there can't be subclasses):

extension Graph: ExpressibleByArrayLiteral where D == Int {
convenience init(arrayLiteral elements: Node...) {
self.init(Array(zip(0..., elements)))
}
}

Python-3.5 typing.Generic subclass never calls `__init__`

Just run it with Python 3.5.2 64bit on Windows, and it just works fine!
If it was a bug, it's fixed now.

Output:

  • main.Foo<~T> foo
  • main.Foo<~T> bar


Related Topics



Leave a reply



Submit