I am stuck on a protocol inheritance problem in Swift. I am trying to construct an API that accepts an array of Protocol Types X. Consumers of this API should be able to then pass in any type of protocol type X OR any sub protocol type X' that inherits from protocol X. It seems this can-not be achieved in Swift without some form of explicitly casting the Protocol type X' back to Protocol type X. Why is this the case? I dont see this behavior with Class Types (I suspect maybe its because the inheritance works markedly differently).

This is my code:

public protocol InjectableService
{}

public protocol ClientDependency
{
    func serviceDependencies() -> [InjectableService.Type]
    func injectDependencies(dependencies: [InjectableService])
}



public protocol TouchIdServiceInterface : InjectableService
{
    func biometricAuthenticate() -> Void
}

public protocol PasswordServiceInterface : InjectableService
{
    func passwordAuthenticate() -> Void
}

public class LoginController : ClientDependency
{
    private var services: [InjectableService]!

    public func injectDependencies(dependencies: [InjectableService]) {
        services = dependencies
    }

    public func serviceDependencies() -> [InjectableService.Type] {
        return [TouchIdServiceInterface.self as! InjectableService.Type , PasswordServiceInterface.self as! InjectableService.Type]
    }


}

As you can see from above in the function serviceDependencies() I have to do this weird casting of the sub protocol type back to its super protocol type. If I remove the cast I get a compiler warning explicitly asking me to do that.

Is there any way to avoid this? I like that with class types it can figure it out for you. I really want to avoid this casting problem because its going to make using the API extremely clunky. Thanks!

1

There are 1 answers

8
bubuxu On

From Apple document:

Metatype Type

A metatype type refers to the type of any type, including class types, structure types, enumeration types, and protocol types.

The metatype of a class, structure, or enumeration type is the name of that type followed by .Type. The metatype of a protocol type—not the concrete type that conforms to the protocol at runtime—is the name of that protocol followed by .Protocol. For example, the metatype of the class type SomeClass is SomeClass.Type and the metatype of the protocol SomeProtocol is SomeProtocol.Protocol.

You can use the postfix self expression to access a type as a value. For example, SomeClass.self returns SomeClass itself, not an instance of SomeClass. And SomeProtocol.self returns SomeProtocol itself, not an instance of a type that conforms to SomeProtocol at runtime.

If you want to return [InjectableService.Type], you need to define classes of each protocol.

public protocol InjectableService
{}

public protocol ClientDependency
{
    func injectDependencies(dependencies: [InjectableService])
    func serviceDependencies() -> [InjectableService.Type]
}

public protocol TouchIdServiceInterface : InjectableService
{
    func biometricAuthenticate() -> Void
}

public protocol PasswordServiceInterface : InjectableService
{
    func passwordAuthenticate() -> Void
}

public class TouchIdService: TouchIdServiceInterface {
    public func biometricAuthenticate() -> Void { }
}

public class PasswordService: PasswordServiceInterface {
    public func passwordAuthenticate() -> Void { }
}


public class LoginController: ClientDependency
{
    private var services: [InjectableService]!

    public func injectDependencies(dependencies: [InjectableService]) {
        services = dependencies
    }

    public func serviceDependencies() -> [InjectableService.Type] {
        let ts: [InjectableService.Type] = [
            TouchIdService.self,
            PasswordService.self
        ]
        return ts
    }

}

let controller = LoginController()
let services = controller.serviceDependencies()

UPDATED

public protocol ClientDependency
{
    func injectDependencies(dependencies: [InjectableService])
    func serviceDependencies() -> [Any]
}

LoginController:

public func serviceDependencies() -> [Any] {
    let ts: [Any] = [
        TouchIdServiceInterface.self,
        TouchIdServiceInterface.self
    ]
    return ts
}

Testing:

let controller = LoginController()
let services = controller.serviceDependencies()
if let touchIdService = services.first as? TouchIdServiceInterface.Protocol {
    print(touchIdService)
}