Coder-256
Coder-256

Reputation: 5618

Swift Generics: Constrain Type Parameter to Protocol

How do you specify that a generic type parameter can only be a protocol (or a protocol conforming to that protocol), and not a class conforming to that protocol?

For example:

import Foundation

@objc protocol MyProtocol {
    var name: String { get }
}

@objc protocol MySubProtocol: MyProtocol {
    func foo()
}

class MyImplementation: MySubProtocol {
    var name: String

    init(name: String) {
        self.name = name
    }

    func foo() {
        print("Foo! Name: \(self.name)")
    }
}

class InterfaceMaker<T: MyProtocol> {
    init() {}

    func makeInterface() -> NSXPCInterface {
        return NSXPCInterface(with: T.self) // Cannot convert value of type 'T.Type' to expected argument type 'Protocol'
    }


    func getProxy() -> T? {
        // Some magic in here involving `NSXPCConnection.remoteObjectProxy`
    }
}

InterfaceMaker<MySubProtocol>().makeInterface() // No error, as expected
InterfaceMaker<MyImplementation>().makeInterface() // No error, but should not work!

How do I specify that a generic type parameter should be a protocol?

I want to constrain T to only protocols conforming to MyProtocol (such as MySubProtocol). But the problem is, I don't know how to prevent T from being a class (such as MyImplementation).

I already tried constraining T.self (I was trying to use where T.self : Protocol, but that caused the error 'self' is not a member type of 'T').

So how do I specify that T must be a protocol conforming to MyProtocol, but not a class? If that's impossible, can I at least specify that T should be any protocol? It's also OK if I need to make either a class-only protocol.

Passing MySubProtocol as a non-generic parameter is not what I am looking for as I would also like to be able to use that protocol as a type for the InterfaceMaker.getProxy() function. Additionally, having that function simply return MyProtocol (in other words, having InterfaceMaker be non-generic) is also not an option.

NOTE: To be fully clear, the reason why I need T to be a protocol is that I am going to pass it to NSXPCInterface.init(with:), which takes a Protocol (which can be obtained by SomeProtocol.self if SomeProtocol is @objc). This means that SomeProtocol.self.Type is or conforms to

If this is not possible, please give a full explanation why. Also mention if it would be possible for this to be supported in a future Swift version.

EDIT: Another way of phrasing this is that T.self is AnyObject.Type should never be true. I would rather check this at compile time rather than a run time check or assertion.

Upvotes: 5

Views: 1334

Answers (1)

barbarity
barbarity

Reputation: 2488

You can check if T conforms to the protocol AnyObject which all classes implicitly conform, but not protocols.

So, in InterfaceMaker:

class InterfaceMaker<T: MyProtocol> {
    init() {}

    func makeInterface() -> NSXPCInterface {
        if T.self is AnyObject.Type {
           //  T is a class
        } else {
           return NSXPCInterface(with: T)
        }
    }


    func getProxy() -> T? {
        // Some magic in here involving `NSXPCConnection.remoteObjectProxy`
    }
}

Upvotes: 2

Related Questions