Home > Software engineering >  Narrowing down the function argument type in class implementing protocol
Narrowing down the function argument type in class implementing protocol

Time:09-08

I have a protocol for simple http client:

protocol HttpClientProtocol {
    func request(route: any RouteProtocol)
}

protocol RouteProtocol {
    associatedtype T : Encodable //this is probably irrelevant, but nevertheless
    // ...stuff
}

Now, I'm trying to use this protocol as a type that will be used by clients (e.g. some MyThingRepository will inject HttpClientProtocol without knowing the specific type that will be injected - for loose coupling). Then I'm trying to create a concrete class from that protocol.

class ApiClient : HttpClientProtocol {
    
    
    func request(route: any ApiRouteProtocol) {
        //... use specific ApiRouteProtocol here, not the general RouteProtocol
    }
    
}

protocol ApiRouteProtocol : RouteProtocol, URLRequestConvertible { }

Problem is that I get Type 'ApiClient' does not conform to protocol 'HttpClientProtocol' compilation error.

My intuition is that if the protocol allows any RouteProtocol, then the class implementing it should be able to narrow down the type to ApiRouteProtocol. However, the compiler doesn't agree. Is my intuition wrong or is it a Swift limitation?

What could I do to work around it? My only working idea for now is to force cast the route to ApiRouteProtocol in ApiClient.

CodePudding user response:

Update

For external access from another module where you can't use an extension I believe the only solution is to declare the functions as it is defined in the protocol and then internally check for the correct conformance

class ApiClient : HttpClientProtocol {
    func request(route: any RouteProtocol) {
        guard let route = route as? (any ApiRouteProtocol) else {
            return // or some error handling
        }
        // do stuff
    }
}

Old solution

One solution is to conform to the protocol but make use of polymorphism to handle calls using ApiRouteProtocol. Not a perfect solution but it works since for any route object that conforms to ApiRouteProtocol the function in the extension will be called.

class ApiClient : HttpClientProtocol {

    func request(route: any RouteProtocol) {
        return // or whatever you want 
    }
}

extension ApiClient {
    func request(route: any ApiRouteProtocol) {
        // do stuff
    }
}

CodePudding user response:

I overcomplicated things by introducing ApiRouteProtocol. I imagined I needed it to conform to UrlRequestConvertible in its extension, but the thing is that I can do a private extension to the RouteProtocol itself and handle the specific implementation required by ApiClient : HttpClientProtocol there.

class ApiClient : HttpClientProtocol {
    
    func request(route: any RouteProtocol) {
        //...route.asURLRequest()...
    }
    
}

extension RouteProtocol {
    func asURLRequest() throws -> URLRequest { //...
}

And now my repository only knows about abstract HttpClientProtocol and RouteProtocol and has no interest in ApiClient and its internals which is what I was trying to achieve here.

  • Related