Swift Bug? Calling Super Class Method When Subclass with Generic Type

Swift bug? calling super class method when subclass with generic type

This works in Xcode 6.1.1 (6A2006)

Sample Image

In the Release notes of 6.1.1 Apple mentions that this bug is fixed:

Class methods and initializers that satisfy protocol requirements now properly invoke subclass overrides when called in generic contexts. (18828217) For example:

protocol P {
class func foo()
}
class C: P {
class func foo() { println("C!") }
}
class D: C {
override class func foo() { println("D!") }
}
func foo<T: P>(x: T) {
x.dynamicType.foo()
}
foo(C()) // Prints "C!"
foo(D()) // Used to incorrectly print "C!", now prints "D!"

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.

swift subclasses used in generics don't get called when inheriting from NSObject

I was able to confirm your results and submitted it as a bug, https://bugs.swift.org/browse/SR-10617. Turns out this is a known issue! I was informed (by good old Hamish) that I was duplicating https://bugs.swift.org/browse/SR-10285.

In my bug submission, I created a clean compact reduction of your example, suitable for sending to Apple:

protocol P {
init()
func doThing()
}

class Wrapper<T:P> {
func go() {
T().doThing()
}
}

class A : NSObject, P {
required override init() {}
func doThing() {
print("A")
}
}

class B : A {
required override init() {}
override func doThing() {
print("B")
}
}

Wrapper<B>().go()

On Xcode 9.2, we get "B". On Xcode 10.2, we get "A". That alone is enough to warrant a bug report.

In my report I listed three ways to work around the issue, all of which confirm that this is a bug (because none of them should make any difference):

  • make the generic parameterized type's constraint be A instead of P

  • or, mark the protocol P as @objc

  • or, don't have A inherit from NSObject


UPDATE: And it turns out (from Apple's own release notes) there's yet another way:

  • mark A's init as @nonobjc

Call method of subclass class by instance of superclass

You can use optional downcast + optional chaining like this:

(point as? AB)?.callAB()

If point is not an instance of AB, nothing will happen

If you need to do call different methods depending on point's type, you can also use switch like this:

switch point {
case let aa as AA: aa.callAA()
case let ab as AB: ab.callAB()
default: break
}

Swift Generics - Allow subclasses passed as parameters

Make test generic:

class Example {
func test<T>(object: SuperClass<T>) {
print(object)
}
}


Related Topics



Leave a reply



Submit