Swift bug? calling super class method when subclass with generic type
This works in Xcode 6.1.1 (6A2006)
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
Create Custom Action in a Class for Use in Interface Builder
Applescript Email Attachment Not Working in Handler
Http Status 415 When Using Alamofire Multipart Upload
3 Component Dynamic Multi UIpickerview Swift
How to Prevent SQL Injections with User-Search-Terms in Vapor 4 (Fluent 4)
Implement a Crosshair Kind Behaviour in Realitykit
Problem with Frameworks in Command Line Tool
Swift: Simple Dispatchqueue Does Not Run & Notify Correctly
How to Convert String to Date Without Time in Swift 3
Siblings Relationship Between Same Models in Vapor
Dynamically Set Properties from Dictionary<String, Any> in Swift
How to Upload Images from The Browser to Amazon S3 Using Vapor and Leaf
Change Color of Row Programmatically in Watchkit