How to check conformance to protocol with associated type in Swift?
TL;DR
Compiler does not have enough information to compare the type until associated type is set.
When you refer to simple protocol, compiler knows its type from the beginning.
But when you refer to protocol with associated type, compiler doesn't know its type until you declare it.
protocol ExampleProtocol {
associatedtype SomeType
func foo(param: SomeType)
}
At this moment for compiler it looks like this:
protocol ExampleProtocol {
func foo(param: <I don't know it, so I'll wait until it's defined>)
}
When you declare a class conforming to the protocol
class A: ExampleProtocol {
typealias SomeType = String
func foo(param: SomeType) {
}
}
Compiler starts to see it like this:
protocol ExampleProtocol {
func foo(param: String)
}
And then it is able to compare types.
Protocol with associatedtype conformance with generic gives compiler error
It doesn't conform because you declared another Error
type inside the class, so everywhere where you use Error
in the required methods, it uses DBClientMock.Error
instead of the protocol-required Swift.Error
.
Either rename DBClientMock.Error
to something else, or change the Error
in methods to Swift.Error
, like below:
// Extension
func get(id: UUID, completion: @escaping (T?, Swift.Error?) -> Void) {
//...
}
func insert(_ model: T, completion: @escaping (Swift.Error?) -> Void) {
//...
}
func delete(_ model: T, completion: @escaping (Swift.Error?) -> Void) {
//...
}
Protocol conformance of system classes using more generic types
The problem occurs due to the fact that Swift is covariant in respect to closure return type, and contra-variant in respect to its arguments. Which means that (VNRequest, Error?) -> Void
can't be used where (Request, Error?) -> Void
is needed (the other way around is possible).
You can solve your problem by using an associated type in the Request
protocol:
public protocol Request {
associatedtype RequestType = Self
var results: [Any]? { get }
var completionHandler: ((RequestType, Error?) -> Void)? { get }
}
The above protocol definition will make the VNRequest
class compile, as the compiler will detect the match for RequestType
.
The downside, though, is that protocols with associated types have some limitations regarding where they can be used, and also passing them as function arguments will require some where
clauses to make them work.
Another alternative would be to use Self
as a parameter to the completion handler:
public typealias RequestCompletionHandler<T> = (T, Error?) -> Swift.Void
public protocol Request {
var results: [Any]? { get }
var completionHandler: RequestCompletionHandler<Self>? { get }
}
This will also solve the conformance issue, but also comes with some constraints: VNRequest
must be final, the functions using Request
must be generic:
func send<R: Request>(_ request: R)
Access associated type of a custom protocol in a where clause on generic types in Swift
The problem is that you're using the reserved word Type
. Try it with some other name like HashType
and it compiles fine.
See "Metatype Type" from the Swift Programming Language:
The metatype of a class, structure, or enumeration type is the name of that type followed by
.Type
.
You should probably get a compiler error on the typealias
line rather than the class
line, and you may want to open a radar on that.
Is there information somewhere on Swift protocol associatedtype using `=` versus `:`?
The =
and the :
are two independent parts of an associated type declaration, rather than being mutually exclusive. This is the full syntax of protocol associated type declaration:
attributes(opt)
access-level-modifier(opt)
'associatedtype'
typealias-name
type-inheritance-clause(opt)
typealias-assignment(opt)
generic-where-clause(opt)
The : TypeName
part is the type-inheritance-clause
, and the = TypeName
is the typealias-assignment
.
: TypeName
constrains what type the associated type can be, namely that it must inherit/conform to TypeName
. This is why : SomeManagerDelegate
didn't work in your case. You are saying that SomeManager.DelegateType
must be some kind of SomeManagerDelegate
, but for Manager
, this is not true - Manager.delegate
is of type ManagerDelegate
, which is a totally unrelated protocol. Even if it were SomeManagerDelegate
, it wouldn't work either because protocols don't conform to themselves.
= TypeName
sets a default type for the associated type. If the compiler cannot infer what the type the associated type for a conformance should be and you didn't say it explicitly it either, it will use that type instead. But in your case, this fact didn't really matter. What actually caused your code to work, wasn't the addition of = SomeManagerDelegate
, but the removal of the constraint : SomeManagerDelegate
. You are no longer constraining what type the associated type should be (it can be anything!), so for Manager
, the associated type can now be inferred to be ManagerDelegate
. Note that you don't have to explicitly say:
typealias DelegateType = ManagerDelegate
In fact, you can totally remove = SomeManagerDelegate
and just say:
associatedtype DelegateType
so it is far from the truth that the =
is "the only thing keeping it together".
This = TypeName
syntax doesn't seem very well documented. Here's a related Swift forums post.
Returning Two Types from one Function using Protocol Conformance
Suppose your method compiles,
Let's create another type that conforms to PriceCalculatable
:
struct Foo : PriceCalculatable {}
Now we try to call your method:
let foo: Foo = PriceCalculator.culculateFinalPrice(for: someProducts, applying: myCoupon)
From the compiler's perspective, the above compiles, yet this results in an inconsistency at runtime. How can the runtime convert a Double
(finalPrice
) into a Foo
?
Therefore, your method should not compile.
To make it work, you can create a ConvertileFromDouble
protocol and make Int
and Double
conform to it. In the protocol, you need to specify an initialiser that takes a Double
as argument.
protocol ConvertibleFromDouble {
init(_ doubleValue: Double)
}
extension Int: ConvertibleFromDouble {
}
extension Double: ConvertibleFromDouble {
}
class PriceCalculator {
static func culculateFinalPrice<T: ConvertibleFromDouble>(for products: [Product],
applying coupon: Coupon?) -> T {
...
Related Topics
iOS 13 Modals - Calling Swipe Dismissal Programmatically
How to Duplicate a Sprite in Sprite Kit and Have Them Behave Differently
Create CSV File in Swift and Write to File
Spritekit Particle Emitter Multi Image
Create a Swift Dictionary Subclass
Swiftui MACos Commands (Menu Bar) and View
Xcode Swift. How to Programmatically Select Cell in View-Based Nstableview
Assemble a List of Users with Geofire/Firebase
Not Getting Expected Delegate Calls When Trying to Restore In-App Purchases with Storekit
Nstableview Inside Nspopover Looks Different as Standalone
Swift 4 - Avfoundation Screen and Audio Recording Using Avassetwriter on MAC Os - Video Frozen
Swift Find Superview of Given Class with Generics
Why Does Type(Of:) Return Metatype, Rather Than T.Type
Swift 3:Appdelegate Does Not Conform to Protocol Gidsignindelegate
How to Use Enumeratedate in Swift 3 to Find All Sundays the Last 50 Years