Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to call initializer for subclass of generic type

Tags:

generics

swift

UPDATE: Question and title have been restated based on changes that were made in response to the initial question.


I've got a base class that implements a generic class method for creating new instances. The simplified logic of this class method is as follows

class MyBaseClass {

    required init(_ record:MyRecordType) {
        println("Entered MyBaseClass.init")
        // Initialize base class from record
    }

    class func retrieveInstance<T:MyBaseClass>(name:String, callback:(T) -> ()) {
        let myRecord:MyRecordType = ... // Perform fetch to get a record for name
        let result = (T.self as T.Type)(myRecord) // Code currently crashes with BAD_ACCESS at this line
        callback(result)
    }
}

I then implement a subclass of this base class with logic like the following

class MySubClass : MyBaseClass {

    required init(_ record:MyRecordType) {
        println("Entered MySubClass.init")
        // Initialize subclass from record
        super.init(record)
    }
}

Next, I try to invoke the generic class method

class AnotherClass {
    func successCallback(result:MySubclass) {
        // Handle callback
    }
    MySubClass.retrieveInstance("objectName", callback:successCallback)
}

When creating an instance of the class - the line marked with a comment identifying the crash location - I am expecting the init method from MySubClass to be invoked. (This odd notation is based on the bug workaround suggested in the replies)

Instead of calling the init method for MySubClass, this code crashes with a BAD_ACCESS. I have been unable to find any nil references or anything else that would explain this crash.

like image 681
Tim Dean Avatar asked Nov 25 '14 02:11

Tim Dean


1 Answers

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.

like image 102
Tim Dean Avatar answered Nov 26 '22 16:11

Tim Dean