Swift Protocol as Generic Parameter to Class
It turns out that all you need to specify a single @objc protocol
that all of your other protocols (which must also be @objc protocol
s) conform to, for example:
import Foundation
@objc protocol SuperProtocol {}
@objc protocol MyProtocol: SuperProtocol {
func foo()
}
class MyConformingClass: MyProtocol {
func foo() {
print("Foo!")
}
}
class ProtocolPrinter<T: SuperProtocol> {
func printT() {
print("T: \(T.self)")
}
func dosomethingWithObject(_ object: T) {
if let object = object as? MyProtocol {
object.foo()
} else {
print("I don't know what object this is: \(object).")
}
}
}
let x = MyConformingClass()
x.foo() // Foo!
MyProtocol.Protocol.self
let myProtocolMeta: Protocol = MyProtocol.self
ProtocolPrinter<MyProtocol>().dosomethingWithObject(MyConformingClass()) // Foo!
How to pass generic protocol as function parameter
You must use Datatype
as a generic constraint:
func set<T: Datatype>(key: String, type: T ,value: T.dataType)
Note how you also need to change the type of the value
parameter to T.dataType
, for this to be type safe. That is, the value you pass in must be the same type as the dataType
of the type
parameter.
Swift Protocol as Generic Parameter
This is a modified version of my type assertion technique. I added the "as? AnyClass" assert so that the type can only be of protocol type. There might be a more elegant way of doing this but going through my notes and research about class assertion this is what I came up with.
import Foundation
protocol IDescribable:class{}
class A:IDescribable{}
class B:A{}
let a = A()
let b = B()
func ofType<T>(instance:Any?,_ type:T.Type) -> T?{/*<--we use the ? char so that it can also return a nil*/
if(instance as? T != nil && type as? AnyClass == nil){return instance as? T}
return nil
}
Swift.print(ofType(a,A.self))//nil
Swift.print(ofType(a,IDescribable.self))//A
Swift.print(ofType(b,B.self))//nil
How to pass protocol with associated type (generic protocol) as parameter in Swift?
If you use typealias
in a protocol to make it generic-like, then you cannot use it as a variable type until the associated type is resolved. As you have probably experienced, using a protocol with associated type to define a variable (or function parameter) results in a compilation error:
Protocol 'MyProtocol' can only be used as a generic constraint because it has Self os associated type requirements
That means you cannot use it as a concrete type.
So the only 2 ways I am aware of to use a protocol with associated type as a concrete type are:
- indirectly, by creating a class that implements it. Probably not what you have planned to do
- making explicit the associated type like you did in your func
See also related answer https://stackoverflow.com/a/26271483/148357
SwifT: Generic function that gets generic protocol as parameter not working
I'm not sure why you say "the protocol only demands that Element implements CustomStringConvertible." The protocol demands that echo
accept and return its Element, which may or may not be String. For example, I can implement this conforming type:
struct AnotherFoo: FooProtocol {
func echo(element: Int) -> Int { fatalError() }
}
So what should happen if I then call:
callEcho(container: AnotherFoo())
This would try to pass a string literal to a function that requires an Int.
Swift - implement protocol with generic method for different T types
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.
Why can't I pass a Protocol.Type to a generic T.Type parameter?
P.Type
vs. P.Protocol
There are two kinds of protocol metatypes. For some protocol P
, and a conforming type C
:
- A
P.Protocol
describes the type of a protocol itself (the only value it can hold isP.self
). - A
P.Type
describes a concrete type that conforms to the protocol. It can hold a value ofC.self
, but notP.self
because protocols don't conform to themselves (although one exception to this rule isAny
, asAny
is the top type, so any metatype value can be typed asAny.Type
; includingAny.self
).
The problem you're facing is that for a given generic placeholder T
, when T
is some protocol P
, T.Type
is not P.Type
– it is P.Protocol
.
So if we jump back to your example:
protocol P {}
class C : P {}
func printType<T>(serviceType: T.Type) {
print(serviceType)
}
let test: P.Type = C.self
// Cannot invoke 'printType' with an argument list of type '(serviceType: P.Type)'
printType(serviceType: test)
We cannot pass test
as an argument to printType(serviceType:)
. Why? Because test
is a P.Type
; and there's no substitution for T
that makes the serviceType:
parameter take a P.Type
.
If we substitute in P
for T
, the parameter takes a P.Protocol
:
printType(serviceType: P.self) // fine, P.self is of type P.Protocol, not P.Type
If we substitute in a concrete type for T
, such as C
, the parameter takes a C.Type
:
printType(serviceType: C.self) // C.self is of type C.Type
Hacking around with protocol extensions
Okay, so we've learnt that if we can substitute in a concrete type for T
, we can pass a C.Type
to the function. Can we substitute in the dynamic type that the P.Type
wraps? Unfortunately, this requires a language feature called opening existentials, which currently isn't directly available to users.
However, Swift does implicitly open existentials when accessing members on a protocol-typed instance or metatype (i.e it digs out the runtime type and makes it accessible in the form of a generic placeholder). We can take advantage of this fact in a protocol extension:
protocol P {}
class C : P {}
func printType<T>(serviceType: T.Type) {
print("T.self = \(T.self)")
print("serviceType = \(serviceType)")
}
extension P {
static func callPrintType/*<Self : P>*/(/*_ self: Self.Type*/) {
printType(serviceType: self)
}
}
let test: P.Type = C.self
test.callPrintType()
// T.self = C
// serviceType = C
There's quite a bit of stuff going on here, so let's unpack it a little bit:
The extension member
callPrintType()
onP
has an implicit generic placeholderSelf
that's constrained toP
. The implicitself
parameter is typed using this placeholder.When calling
callPrintType()
on aP.Type
, Swift implicitly digs out the dynamic type that theP.Type
wraps (this is the opening of the existential), and uses it to satisfy theSelf
placeholder. It then passes this dynamic metatype to the implicitself
parameter.So,
Self
will be satisfied byC
, which can then be forwarded ontoprintType
's generic placeholderT
.
Why is T.Type
not P.Type
when T == P
?
You'll notice how the above workaround works because we avoided substituting in P
for the generic placeholder T
. But why when substituting in a protocol type P
for T
, is T.Type
not P.Type
?
Well, consider:
func foo<T>(_: T.Type) {
let t: T.Type = T.self
print(t)
}
What if we substituted in P
for T
? If T.Type
is P.Type
, then what we've got is:
func foo(_: P.Type) {
// Cannot convert value of type 'P.Protocol' to specified type 'P.Type'
let p: P.Type = P.self
print(p)
}
which is illegal; we cannot assign P.self
to P.Type
, as it's of type P.Protocol
, not P.Type
.
So, the upshot is that if you want a function parameter that takes a metatype describing any concrete type that conforms to P
(rather than just one specific concrete conforming type) – you just want a P.Type
parameter, not generics. Generics don't model heterogenous types, that's what protocol types are for.
And that's exactly what you have with printType(conformingClassType:)
:
func printType(conformingClassType: P.Type) {
print(conformingClassType)
}
printType(conformingClassType: test) // okay
You can pass test
to it because it has a parameter of type P.Type
. But you'll note this now means we cannot pass P.self
to it, as it is not of type P.Type
:
// Cannot convert value of type 'P.Protocol' to expected argument type 'P.Type'
printType(conformingClassType: P.self)
Swift Generics: Conforming to protocol by constraining generic parameters to variable types defined by said protocol
The protocol says a different thing from what your implementation says.
Fact 1
According to the protocol this getter
var assignment: Assignment { get }
can return any value conforming to Assignment
.
Fact 2
On the other hand your implementation here
var assignment: A
says that assignment
will contain a value of a specific type A
(which happens to conform to Assignment
).
These are 2 very different statements.
The fix
Here's an easy fix
protocol AssignmentTimeLog {
associatedtype A: Assignment
associatedtype B: TimeLog
var assignment: A { get }
var timeLog: B { get }
}
struct MyAssignmentTimeLog<A, T>: AssignmentTimeLog where A: Assignment, T: TimeLog {
var assignment: A
var timeLog: T
}
Related Topics
Swift- How to Display Image Over Button
Applewatch Messages Url Works Hard Coded But Not with Variables
Avplayer Seektotime Not Working Properly
Selectively Remove and Delete Objects from a Nsmutablearray in Swift
Drag and Drop Image from Uicollectionview to Uiview
Migration from Swift 3 to Swift 4 - Cannot Convert String to Expected String.Element
How to Trigger Updateuiview of a Uiviewrepresentable
How to Import Googleanalytics Header into a Library Framework
Using Decodable with Inheritance Raises an Exception
Metal Kernels Not Behaving Properly on the New MACbook Pro (Late 2016) Gpus
Navigationview Bar Material Invisible on iOS 15
How to Add 3D Shapes in Swift Ui
How to Cancel Firebase Setvalue While Pending for Completion (When Offline)