swift subclasses in function types - swift

Is there a way to assign a function with a parameter that is a subclass to a function variable with a parameter that is its superclass? Here is an example of what I mean:
class ClassA {}
class subclassOfA:ClassA {}
func subclassToNil(argument:subclassOfA) -> (){}
var functionVariable:(ClassA->())
funcVar = subclassToNil
This raises a type incompatibility exception.

I'm afraid not--you've discovered "covariance" and "contravariance". Function types are contravariant with their parameters (arguments), which means you could supply a superclass if you wanted, but not a subclass. With return values on the other hand, function types are are covariant and could return a subclass if you'd like.
With a little thought, these rules make sense:
class ClassA {}
class SubclassOfA: ClassA {}
func subclassToNil(argument: SubclassOfA) -> ()) {}
var functionVariable: (ClassA -> ())
functionVariable = subclassToNil
functionVariable(ClassA()) //`subclassToNil` won't know what to do with this; kablooie!
However:
class ClassParent {}
class ClassA: ClassParent {}
func subclassToNil(argument: ClassParent) -> ()) {}
var functionVariable:(ClassA -> ())
functionVariable = subclassToNil
functionVariable(ClassA()) //`ClassA()` is indeed a valid `ClassParent`, so we're fine.
So it's safe to use parameters that are less specific. The reasoning for return values is very similar, and you'll see that logically, you can use ones that are more specific.

Related

Swift generic T.Type becomes T.Protocol

Swift 5.1
I'm writing a class that has a generic parameter T, and one of its methods accepts a type as an argument, where the type extends from T.Type. See foo1 below:
public protocol P {
}
public class C: P {
}
public class X<T> {
public func foo1(_ t: T.Type) { // The function in question
}
public func foo2(_ t: P.Type) { // Note that this works as expected, but is not generic
}
}
public func test() {
let x = X<P>()
x.foo1(C.self) // ERROR Cannot convert value of type 'C.Type' to expected argument type 'P.Protocol'
x.foo2(C.self) // Works as expected, but is not generic
}
Now, foo1 works fine when T is a class. However, when T is a protocol (e.g. P), Swift seems to rewrite my function signature from T.Type to T.Protocol.
Why did it do this?
How do I instead get foo1 to accept a type that inherits from P?
Class X is used in a number of other places - any changes to it must not restrict or remove class parameter T nor reduce X's functionality, nor make explicit reference to C or P. (It would be acceptable to constrain T to exclude protocols that do not extend AnyObject; I don't know how to do that, though. It might also be acceptable to e.g. create a subclass of X that adds the ability to handle a protocol in T, but I'm not sure how to do that, either.)
For clarity, this class is used to register classes (t) that conform to some specified parent (T), for more complicated project reasons. (Note that classes are being registered, not instances thereof.) The parent is given at the creation of X, via the type parameter. It works fine for a T of any class, but I'd also like it to work for a T of any protocol - or at least for a T of any protocol P: AnyObject, under which circumstances foo1 should accept any subclass of P...the same way it works when T is a class.
Even though C is a P, but C.Type != P.Type, so the error.
But generics works in a bit different way, like in below examples:
public class X {
public func foo1<T>(_ t: T.Type) { // this way
}
public func foo3<T:P>(_ t: T.Type) { // or this way
}
public func foo2(_ t: P.Type) { // Note that this works as expected, but is not generic
}
}
public func test() {
let x = X()
x.foo1(C.self) // << works
x.foo3(C.self) // << works
x.foo2(C.self) // Works as expected, but is not generic
}

Generic Constraints and Initializer Inheritance in Swift

I am trying to call an initializer required by protocol A on a type that both conforms to A and is a subclass of C.
All is fine and good if C is a base class. But as soon as C subclasses another class, say B, it breaks.
Here's what I am talking about:
protocol A {
init(foo: String)
}
class B {
init() {}
}
class C: B {}
func makeSomething<T: A>() -> T where T: B {
return T(foo: "Hi")
}
That works. But if I change where T: B to where T: C, I get the following error: argument passed to call that takes no arguments. It will only allow me to call Bs init.
Why can't I call this initializer? I understand that the super class has its own designated initializers that must be called. But that will be enforced when someone actually writes the class that subclasses B and conforms to A. (E.g. implementing init(Foo: String) and calling super.init(bar: Int) inside).
Any help here would be greatly appreciated. Thanks!
Swift provides a default initializer for your base class if it has all properties initialized but not incase of Generics because Swift may think its properties has not been initialized.
So when you constraint your return type as
func makeSomething<T: A>() -> T where T: C
It requires initialization for class C as Swift cannot provide a default initializer.But if your remove the where clause everything works fine
func makeSomething<T: A>() -> T {
return T(foo:"string")
}
If you want to return return T(foo: "Hi") :
You are getting error because class C doesn't have initializer that accepts init(foo: String)
Change your class C as
class C: A {
required init(foo: String) {
}
}
which provides at least one initializer that accepts the argument type.
So, remember if you don't subclass There is already one initializer doing your job and you dont get error.

Swift: Return class constrained with generic type

I have a basic generic class:
class SharedClass<T> {}
And a builder for it:
class SharedClassBuilder {
func build<T>() -> SharedClass<T>? {
return ...
}
}
What I want to be able to do, is return an instance that inherits SharedClass, and conforms to T. For example:
protocol MyObject {
func doIt()
}
var result: SharedClass<MyObject>? = SharedClassBuilder().build()
result?.doIt()
Unfortunately, the Swift compiler complains that the returned type does not have a member named doIt.
Is there a way to achieve what I'm looking for?
I suspect it's not so much that you want the returned class to be constrained by the generic type, as you're asking the returned class to be an instance of the constrained type. In your snippet, you're expecting the unwrapped result to conform to MyObject. Taking this a step further, it means that the conformance of SharedClass is determined entirely from how it was constructed. As far as I know this isn't supported in Swift.
However, there's nothing stopping you having a member of SharedClass that is a T. Something along the lines of:
class SharedClass<T> {
var v : T?
}
class SharedClassBuilder {
func build<T>() -> SharedClass<T>? {
return SharedClass()
}
}
protocol MyObject {
func doIt()
}
var result: SharedClass<MyObject>? = SharedClassBuilder().build()
result?.v?.doIt()

Swift: Method overriding in parameterized class

I'm very new to Swift but I have some experience with OO-programming. I've started to try and use parameterized classes in Swift and I have come across a strange design feature when overloading methods. If I define the following classes:
class ParameterClassA {
}
class ParameterClassB: ParameterClassA {
}
class WorkingClassA<T: ParameterClassA> {
func someFunction(param: T) -> Void {
}
}
class WorkingClassB: WorkingClassA<ParameterClassB> {
override func someFunction(param: ParameterClassA) {
}
}
Then the code compiles fine. However, as you'll notice, I've overloaded the function that normally uses the parameter type, which in my example is ParameterClassB, and given it a parameter of type ParameterClassA. How is that supposed to work? I know that it's not allowed in Java, and I'm wondering how the type parameter is interpreted. Can it be anything from the class hierarchy of the type parameter?
Also note that the problem is exactly the same if I remove the type parameter constraint : ParameterClassA in WorkingClassA.
If I remove the override keyword, then I get a compiler error requesting that I add it.
Thanks a lot for any explanation!
It has nothing at all to do with the generics (what you call "parameterized"). It has to do with how one function type is substitutable for another in Swift. The rules is that function types are contravariant with respect to their parameter types.
To see this more clearly, it will help to throw away all the misleading generic stuff and the override stuff, and instead concentrate directly on the business of substituting one function type for another:
class A {}
class B:A {}
class C:B {}
func fA (x:A) {}
func fB (x:B) {}
func fC (x:C) {}
func f(_ fparam : B -> Void) {}
let result1 = f(fB) // ok
let result2 = f(fA) // ok!
// let result3 = f(fC) // not ok
We are expected to pass to the function f as its first parameter a function of type B -> Void, but a function of type A -> Void is acceptable instead, where A is superclass of B.
But a function of type C -> Void is not acceptable, where C is subclass of B. Functions are contravariant, not covariant, on their parameter types.
#matt is completely right about why this works – it's due to the fact that method inputs are contravariant, instead of covariant.
This means that you can only override a given function with another function that has a broader (or the same) input type – meaning that you can substitute in a superclass argument in place of a subclass argument. This may seem completely backwards at first – but it makes total sense if you think about it a bit.
The way I would explain it in your situation is with a slightly stripped down version of your code:
class ParameterClassA {}
class ParameterClassB: ParameterClassA {}
class WorkingClassA {
func someFunction(param: ParameterClassB) {}
}
class WorkingClassB: WorkingClassA {
override func someFunction(param: ParameterClassA) {}
}
Note here that WorkingClassB is overriding the someFunction with ParameterClassA – the superclass of ParameterClassB.
Now, imagine you have an instance of WorkingClassA, and then call someFunction on this instance, with an instance of ParameterClassB:
let workingInstanceA = WorkingClassA()
workingInstanceA.someFunction(ParameterClassB()) // expects ParameterClassB
So far, nothing unusual. We passed a ParameterClassB instance to a function that expects a ParameterClassB.
Now let's assume that you swap your WorkingClassA instance with a WorkingClassB instance. This is perfectly legal in OOP – as the subclass can do everything the superclass could do.
let workingInstanceB = WorkingClassB()
workingInstanceB.someFunction(ParameterClassB()) // expects ParameterClassA
So what happens now? We're still passing a ParameterClassB instance into the function. However, now the function expects a ParameterClassA instance. Passing a subclass into an argument that expects a superclass is legal in OOP (this is covariance), as the subclass can do everything that the superclass could do – therefore this doesn't break anything.
Because the function signature can only get more broad (or remain unchanged) as you override it, it ensures that you can always pass it the original argument type that the function defined, as any superclass argument in an overridden version can accept it.
If you think about the reverse for a second, you'll see why it couldn't possibly work. As the function would get more restrictive as it gets overridden, it won't be able to accept arguments that the superclass could originally accept – therefore it cannot work.

Implementing Swift protocol methods in a base class

I have a Swift protocol that defines a method like the following:
protocol MyProtocol {
class func retrieve(id:String) -> Self?
}
I have several different classes that will conform to this protocol:
class MyClass1 : MyProtocol { ... }
class MyClass2 : MyProtocol { ... }
class MyClass3 : MyProtocol { ... }
The implementation for the retrieve method in each subclass will be nearly identical. I'd like pull the common implementation of those functions into a shared superclass that conforms to the protocol:
class MyBaseClass : MyProtocol
{
class func retrieve(id:String) -> MyBaseClass?
}
class MyClass1 : MyBaseClass { ... }
class MyClass2 : MyBaseClass { ... }
class MyClass3 : MyBaseClass { ... }
The problem with this approach is that my protocol defines the return type of the retrieve method as type Self, which is what I really want in the end. However, as a result I cannot implement retrieve in the base class this way because it causes compiler errors for MyClass1, MyClass2, and MyClass3. Each of those classes must conform to the protocol that they inherit from MyBaseClass. But because the method is implemented with a return type of MyBaseClass and the protocol requires it to be of MyClass1, it says that my class doesn't conform to the protocol.
I'm wondering if there is a clean way of implementing a protocol method that references a Self type in one or more of its methods from within a base class. I could of course implement a differently-named method in the base class and then have each subclass implement the protocol by calling into its superclass's method to do the work, but that doesn't seem particularly elegant to me.
Is there a more straightforward approach that I'm missing here?
This should work:
protocol MyProtocol {
class func retrieve(id:String) -> Self?
}
class MyBaseClass: MyProtocol {
required init() { }
class func retrieve(id:String) -> Self? {
return self()
}
}
required init() { } is necessary to ensure any subclasses derived from MyBaseClass has init() initializer.
Note that this code crashes Swift Playground. I don't know why. So try with real project.
Not sure what you're looking to accomplish here by just your example, so here's a possible solution:
protocol a : class {
func retrieve(id: String) -> a?
}
class b : a {
func retrieve(id: String) -> a? {
return self
}
}
The reasoning behind the
protocol a : class
declaration is so that only reference types can be extensions. You likely don't want to be passing around value types (struct) when you're dealing with your classes.
I have marked the answer from #rintaro as the correct answer because it did answer the question as I asked it. However, I have found this solution to be too limiting so I'm posting the alternate answer I found to work here for any others running into this problem.
The limitation of the previous answer is that it only works if the type represented by Self (in my example that would be MyClass1, MyClass2, or MyClass3) is used in a protocol or as the return type from a class method. So when I have this method
class func retrieve(id:String) -> Self?
everything works as I hoped. However, as I worked through this I realized that this method now needs to be asynchronous and can't return the result directly. So I tried this with the class method:
class func retrieve(id:String, successCallback:(Self) -> (), failureCallback:(NSError) -> ())
I can put this method into MyProtocol but when I try to implement in MyBaseClass I get the following compiler error:
Error:(57, 36) 'Self' is only available in a protocol or as the result of a class method; did you mean 'MyBaseClass'?
So I really can't use this approach unless the type referenced by Self is used in very specific ways.
After some experimentation and lots of SO research, I was finally able to get something working better using generics. I defined the method in my protocol as follows:
class func retrieve(id:String, successCallback:(Self) -> (), failureCallback:(NSError) -> ())
and then in my base class I do the following:
class MyBaseClass : MyProtocol {
class func retrieve<T:MyBaseClass>(id:String, successCallback: (T) -> (), failureCallback: (NSError) -> ()) {
// Perform retrieve logic and on success invoke successCallback with an object of type `T`
}
}
When I want to retrieve an instance of the type MyClass1, I do the following:
class MyClass1 : MyBaseClass {
func success(result:MyClass1} {
...
}
func failure(error:NSError) {
...
}
class func doSomething {
MyClass1.retrieve("objectID", successCallback:success, failureCallback:failure)
}
With this implementation, the function type for success tells the compiler what type should be applied for T in the implementation of retrieve in MyBaseClass.