Swift Covariant Generic Function:Placeholder Type Is a Subclass of Another

Swift 3: Xcode claims that a non-generic class is generic


Does the base class have to be non-generic for the subclass to be visible to Obj-C?

Yes. The subclass does not itself have a generic placeholder in its definition, but a subclass "is" its superclass. A Dog "is" a Quadruped (if Dog is a subclass of Quadruped). So this class "is" a GenericFRCTableVC<Site>. Well, Objective-C can make no sense of a GenericFRCTableVC<Site> so it can make no sense of an ASSitesTableViewController.

After converting to Swift 3

I think you'll find you would have had the same problem in an earlier version of Swift. I don't believe this has anything to do with the conversion to Swift 3.

Accessing optional method in protocol returns scope error


class MyClass: NSObject, MyProtocol { ... }

This says that MyClass implements MyProtocol, which allows, but does not require myFunction. The compiler can clearly see that MyClass does not implement myFunction, so self.myFunction() isn't a thing. It could be a thing, but it's clearly not a thing. The protocol doesn't play into this.

You could "work around" this by saying "I don't mean MyClass; I mean MyProtocol":

func doSomething() {
(self as MyProtocol).myFunction?()
}

But this is kind of silly IMO. self doesn't implement myFunction and this is known at compile time.

If the point is to implement this via subclassing, the correct way to do that is to just implement it as empty in the superclass:

class MyClass: NSObject, MyProtocol {
func myFunction() {}

func doSomething() {
myFunction()
}
}

This is precisely equivalent to "do nothing" while allowing overrides in subclasses.

Swift: Overriding Self-requirement is allowed, but causes runtime error. Why?

Yes, there seems to be a contradiction. The Self keyword, when used as a return type, apparently means 'self as an instance of Self'. For example, given this protocol

protocol ReturnsReceived {

/// Returns other.
func doReturn(other: Self) -> Self
}

we can't implement it as follows

class Return: ReturnsReceived {

func doReturn(other: Return) -> Self {
return other // Error
}
}

because we get a compiler error ("Cannot convert return expression of type 'Return' to return type 'Self'"), which disappears if we violate doReturn()'s contract and return self instead of other. And we can't write

class Return: ReturnsReceived {

func doReturn(other: Return) -> Return { // Error
return other
}
}

because this is only allowed in a final class, even if Swift supports covariant return types. (The following actually compiles.)

final class Return: ReturnsReceived {

func doReturn(other: Return) -> Return {
return other
}
}

On the other hand, as you pointed out, a subclass of Return can 'override' the Self requirement and merrily honor the contract of ReturnsReceived, as if Self were a simple placeholder for the conforming class' name.

class SubReturn: Return {

override func doReturn(other: Return) -> SubReturn {
// Of course this crashes if other is not a
// SubReturn instance, but let's ignore this
// problem for now.
return other as! SubReturn
}
}

I could be wrong, but I think that:

  • if Self as a return type really means 'self as an instance of
    Self', the compiler should not accept this kind of Self requirement
    overriding, because it makes it possible to return instances which
    are not self; otherwise,

  • if Self as a return type must be simply a placeholder with no further implications, then in our example the compiler should already allow overriding the Self requirement in the Return class.

That said, and here any choice about the precise semantics of Self is not bound to change things, your code illustrates one of those cases where the compiler can easily be fooled, and the best it can do is generate code to defer checks to run-time. In this case, the checks that should be delegated to the runtime have to do with casting, and in my opinion one interesting aspect revealed by your examples is that at a particular spot Swift seems not to delegate anything, hence the inevitable crash is more dramatic than it ought to be.

Swift is able to check casts at run-time. Let's consider the following code.

let sm = SuperMario()
let ffm = sm as! FireFlowerMario
ffm.throwFireballs()

Here we create a SuperMario and downcast it to FireFlowerMario. These two classes are not unrelated, and we are assuring the compiler (as!) that we know what we are doing, so the compiler leaves it as it is and compiles the second and third lines without a hitch. However, the program fails at run-time, complaining that it

Could not cast value of type
'SomeModule.SuperMario' (0x...) to
'SomeModule.FireFlowerMario' (0x...).

when trying the cast in the second line. This is not wrong or surprising behaviour. Java, for example, would do exactly the same: compile the code, and fail at run-time with a ClassCastException. The important thing is that the application reliably crashes at run-time.

Your code is a more elaborate way to fool the compiler, but it boils down to the same problem: there is a SuperMario instead of a FireFlowerMario. The difference is that in your case we don't get a gentle "could not cast" message but, in a real Xcode project, an abrupt and terrific error when calling throwFireballs().

In the same situation, Java fails (at run-time) with the same error we saw above (a ClassCastException), which means it attempts a cast (to FireFlowerMario) before calling throwFireballs() on the object returned by queryFriend(). The presence of an explicit checkcast instruction in the bytecode easily confirms this.

Swift on the contrary, as far as I can see at the moment, does not try any cast before the call (no casting routine is called in the compiled code), so a horrible, uncaught error is the only possible outcome. If, instead, your code produced a run-time "could not cast" error message, or something as gracious as that, I would be completely satisfied with the behaviour of the language.

Self in init params

Instead of using Self or A in each of the initialisers, you can simply override each subclass' initialiser to use its own type as operation.

This works because A's initialiser states that operation should be a type that conforms to A, and when you override it you have the liberty to use a subclass of A as operation instead. However, if you change operation to an unrelated type such as String or Int, the compiler will not override the existing initialiser.

Firstly, define A with its init:

class A {
init(finishBlock: ((_ operation: A) -> Void)?) {...}
}

Now to create a subclass, you must override init using the subclass' type as operation instead. In your call to super.init, force upcast operation ($0) to your subclass' type, and call finishBlock with this casted operation.

class B: A {
override init(finishBlock: ((_ operation: B) -> Void)?) {
// Perform custom initialisation...
super.init { finishBlock?($0 as! B) }
}

func fooOnlyInB() {
print("foo")
}
}

B's initialiser now passes B as operation, which means that you don't need to cast it yourself anymore! This is thanks to the fact that you can override an init with a more specific type, in this case B.

let b = B { operation in
operation.fooOnlyInB() // prints "foo"
}


Related Topics



Leave a reply



Submit