Swift: How to Make a Function with a Subclass Return Type Conform to a Protocol, Where a Superclass Is Defined as a Return Type

Swift: How can I make a function with a Subclass return type conform to a protocol, where a Superclass is defined as a return type?

Before you go much further, I'd recommend some background reading on covariance vs contravariance and the Liskov substitution principle.

  • Return types for methods overridden when subclassing are covariant: the subclass override of a method can return a subtype of the superclass method's return type.

  • Generic type parameters are invariant: a specialization can neither narrow nor expand the type requirements.

The relationship between a protocol and a concrete type that adopts it is more like generics than like subclassing, so return types declared in protocols are invariant, too. (It's hard to say exactly why on first read. Possibly something about existential vs constraint-only protocols?)

You can allow covariance in a protocol by specifying associated type requirements, though:

protocol SomeProtocol {
associatedtype ReturnType: SuperclassType
func someFunction(someParameter: SomeType) -> ReturnType
}

class SomeClass : SomeProtocol {
func someFunction(someParameter: SomeType) -> SubclassType { /*...*/ }
}

Now, it's clear that the return type of someFunction in a type adopting SomeProtocol must be either SuperclassType or a subtype thereof.

In swift, how do I return an object of the same type that conforms to a protocol

Autocomplete will help you, but basically, if Foo is a class, you just need a method which matches the exact signature of the method in your protocol:

class Foo {}

protocol JSONInitializable {
static func initialize(fromJSON: [String : AnyObject]) throws -> Self?
}

extension Foo: JSONInitializable {
static func initialize(fromJSON: [String : AnyObject]) throws -> Self? {
return nil
}
}

As a note here, you will have to, within this method, replace all instances of Foo with Self. If you want to use a constructor, it will have to be one marked as required.

With this in mind, it probably makes more sense to change your protocol to requiring an initializer rather than a static method called initialize, in which case, take a look at Blake Lockley's answer. However, this answer stands to answer the question of how to deal with Self in protocols, as there are certainly cases where we might want a method that returns Self that isn't an initializer.


If Foo is not a class, it is a value type which cannot be subclasses, and as such, we return the name of the type:

struct Foo: JSONInitializable {
static func initialize(fromJSON: [String : AnyObject]) throws -> Foo? {
return nil
}
}
enum Foo: JSONInitializable {
case One, Two, Three

static func initialize(fromJSON: [String : AnyObject]) throws -> Foo? {
return nil
}
}

The reason you need to return Self? from the method in the case of a class is because of inheritance. The protocol declares that if you conform to it, you will have a method called initialize whose return type will be an optional version of whatever you are.

If we write Foo's initialize method as you wrote it, then if we subclass Foo with Bar, then now we have broken our conformance to the protocol. Bar has inherited the conformance to the protocol from Foo, but it doesn't have a method which is called initialize and returns Bar?. It has one that returns Foo.

Using Self here means that when our subclasses inherit this method, it will return whatever type they are. Self is Swift's equivalent of Objective-C's instancetype.

How is Self in protocol interpreted in subclass?

According to the documentation Self in this case will be A since A is the one conforming to the protocol, B is only doing it indirectly as a subclass of A.

So when A conforms to Copyable you are saying that A and all its subclasses must have an init(_ instance: A)

In a protocol declaration or a protocol member declaration, the Self type refers to the eventual type that conforms to the protocol.

You can actually test this by removing the required init(_ instance: A) init and you will get an error even if you have an init(_ instance: B), so since A is the class conforming to the protocol you must have an init where the instance argument is A

How to make return type of function generic in Swift

This isn't possible.

What generics are for

Suppose you have this function:

func identity(_ value: Any) -> Any {
return value
}

It doesn't actually work:

let i = 5
assert(identity(i) == i) // ❌ binary operator '==' cannot be applied to operands of type 'Any' and 'Int'

Any causes type information to be lost. Even though we see that the type of the parameter and the return value will always be the same, we haven't expressed that to the type system. This is a perfect use-case for a generic type parameter. It allows us to express the relationships between the types of the parameter and the return value.

func identity<T>(_ value: T) -> T {
return value
}

let i = 5
assert(identity(i) == i) // ✅

What generics are not for

Looking back at your problem, you see that there are no type relationships here to be expressed.

  • ClassA.validateModel() always returns [String]
  • ClassB.validateModel() always returns [Int]
  • ClassC.validateModel() always returns [MyCustomEnum]

That's not generic.

How would it even work?

Suppose you had an object of type ElementData. That object could be an instance of ElementData, or of ClassA, or ClassB, or ClassC. Given that all four of these types are possible, and assuming there exists some concoction to do what you want, how would this code work?

let elementData = someElementData()
let validatedModel = elementData.validateModel() // What type is `someValue` supposed to be?

Since we (nor the compiler) knows what concrete type the value of elementData will be (we only know that it's an ElementData, or one of its subclasses), how is the compiler supposed to determine the type of validatedModel ?

Furthermore, your code would be breaking the Liskov Substitution Principle. ClassA needs to support being substituted where ElementData is expected. One of the things that ElementData.validateModel()can do is return a Something. Thus, ClassA.validateModel() needs to either return a Something, or a subclass (strangely, it seems that only inheritence relationships work, not protocol subtype relationships. For example, returning Int where Any is expected doesn't work). Since ClassA.validateModel() returns Array<String>, and Array isn't a class (thus, can't have a superclass), there's no possible type Something that could be used to make the code not violate LSP and compile.

Here's an illustration of LSP, and how Covariance works in return types of overridden methods, and not in parameter types of overridden methods.

// https://www.mikeash.com/pyblog/friday-qa-2015-11-20-covariance-and-contravariance.html

class Animal {}
class Cat: Animal {}

class Person {
func purchaseAnimal() -> Animal {
return Animal()
}
}

class CrazyCatLady: Person {
// Totally legal. `Person` has to be able to return an `Animal`.
// A `Cat` is an animal, so returning a `Cat` where an `Animal` is required is totally valid
override func purchaseAnimal() -> Cat {
return Cat()
}

// This method definition wouldn't be legal, because it violates the Liskov Substitution Principle (LSP).
// A `CrazyCatLady` needs to be able to stand in anywhere a `Person` can be used. One of the things a
// `Person` can do is to `pet(animal: Animal)`. But a `CrazyCatLady` can't, because she can only pet cats.
//
// If this were allowed to compile, this could would be undefined behaviour:
//
// let person: Person = getAPerson()
// let animal: Animal = getAnAnimal()
// person.pet(animal)
//
// override func pet(animal: Cat) { // ❌ method does not override any method from its superclass
//
// }
}

One approach to a solution

First of all, we need to establish what's in common between these return types. If we could do that, then the compiler has an answer to the question of "what type should someModel be?" above.

There are two tools available:

  1. Class inheritance (subclasses are subtypes of their superclasses)
  2. Protocol conformance (protocol conforming types are subtypes of the protocols they conform to)

Both have advantages/disadvantages. Protocols force you in the painful road of dispair that is associated-type, whereas Classes are less flexible (since they can't be subclassed by enums or structs). In this case, the answer lies with what you want this code to do. Fundamentally, you're trying to hook this data up to a table cell. So make a protocol for that:

protocol CellViewDataSource {
func populate(cellView: UICellView) {
// adjust the cell as necessary.
}
}

Now, update your methods to return this type:

class ElementData {
func validateModel() -> CellViewDataSource {
fatalError()
}
}

class ClassA {
func validateModel() -> CellViewDataSource {
fatalError()
}
}

To implement these methods, you'll have to extend Array to conform to CellViewDataSource. However, that's a pretty terrible idea. I suggest instead that you create a new type (probably a struct) that stores the data you need.

struct ModelA {
let name: String
let points: Int
let description: String
let image: UIImage
}

extension ModelA: CellViewDataSource {
func populate(cellView: UICellView) {
// Populate the cell view with my `name`, `points`, `description` and `image`.
}
}

class ElementData {
func validateModel() -> CellViewDataSource {
fatalError("Abstract method.")
}
}

class ClassA {
func validateModel() -> CellViewDataSource {
return ModelA(
name: "Bob Smith",
points: 123,
description: "A dummy model.",
image: someImage()
)
}
}

Access variable through a protocol type in my case

you can cherrypick:

option-1

make the protocol extension only for Bar instances who conforms Foo:

extension Foo where Self == Bar { // or Self: Bar it is up to what you want
func someJob() {
self.name = "..."
}
}

option-2

try to downcast self as Bar in the method's body:

extension Foo {
func someJob() {
if let barSelf = self as? Bar {
barSelf.name = "..."
}
}
}


Related Topics



Leave a reply



Submit