1

I have a Client protocol that looks like this:

protocol Client {
  func get<T>(_ url: String) -> Promise<T>
}

Now, the problems start when I try to implement it. I want to have 2 types of clients: AlamofireClient and MockClient (maybe in the future more, like ErrorClient etc. but let's start simple)

so I need something like:

final class AlamofireClient: Client {
   func get<T: Decodable>(_ url: String) -> Promise<T> {
        (...)
   }
}

and:

final class MockClient: Client {
   func get<T: Mockable>(_ url: String) -> Promise<T> {
            return Promise { seal in
               seal.fulfill(T.mock())
        }
   }
}

here the simple Mockable interface that every entity in the app will implement:

public protocol Mockable {
    static func mock() -> Self
}

But I always get the error:

Type 'MockClient' does not conform to protocol 'Client'
Type 'AlamofireClient' does not conform to protocol 'Client'

That's because is not the same as <T: Decodable> and <T: Mockable>

Is there an elegant way to solve this issue? I'm not able to find an elegant solution for this problem. Maybe the whole idea is just bad? I also tried solving the problem with PATs but also no luck there.

1
  • 1
    A protocol is a set of instructions for what you have to do in order to conform to the protocol. You must match what it says. Your instructions say func get<T>(_ url: String) -> Promise<T> and that is what a class must declare in order to conform. You cannot say some other thing. Commented Mar 1, 2021 at 19:39

1 Answer 1

2

The way you have it written, by conforming to the protocol Client, you have to implement a function get, with a generic, unconstrained argument T. In the example implementations provided, you added a type constraint to the generic parameter T, which does not match the function in the protocol.

There's more than one way you can approach a solution to this problem. Keeping in mind that you said all entities will conform to Mockable, the solution that requires the least change to the code you provided is to use protocol composition to enforce that all parameters T conform to both Decodable and Mockable.

protocol Client {
  func get<T: Decodable & Mockable>(_ url: String) -> Promise<T>
}

In your clients, you would implement that function exactly as written.

Now, in your MockClient, you can call T.mock(), and in your real implementations, you can treat T as Decodable as required. Of course, this now requires that even your mock arguments conform to Decodable, but I would assume your mock arguments will be fairly lightweight and thus this wouldn't be a problem.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank You for you answer! I will test it out today however I have one question, won't this solution be problematic in the future? Let's say I want to put e.g "Errorable" or some other able protocols (can't think of anything atm but who knows what the future brings) then the function signature <T: a lot of ables> will grow really big and become really confusing for newcomers to the codebase. What do you think?
One way to make things a bit easier to look at would be to use a typealias. For example, typealias NetworkResult = Decodable & Mockable & Errorable. In that way, you can "compose" the proper protocol type for the use case.
Thank You! I did it exactly that way and everything works fine :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.