In Swift, how to cast to protocol with associated type?
Unfortunately, Swift doesn't currently support the use of protocols with associated types as actual types. This however is technically possible for the compiler to do; and it may well be implemented in a future version of the language.
A simple solution in your case is to define a 'shadow protocol' that SpecialController
derives from, and allows you to access currentValue
through a protocol requirement that type erases it:
// This assumes SpecialValue doesn't have associated types – if it does, you can
// repeat the same logic by adding TypeErasedSpecialValue, and then using that.
protocol SpecialValue {
// ...
}
protocol TypeErasedSpecialController {
var typeErasedCurrentValue: SpecialValue? { get }
}
protocol SpecialController : TypeErasedSpecialController {
associatedtype SpecialValueType : SpecialValue
var currentValue: SpecialValueType? { get }
}
extension SpecialController {
var typeErasedCurrentValue: SpecialValue? { return currentValue }
}
extension String : SpecialValue {}
struct S : SpecialController {
var currentValue: String?
}
var x: Any = S(currentValue: "Hello World!")
if let sc = x as? TypeErasedSpecialController {
print(sc.typeErasedCurrentValue as Any) // Optional("Hello World!")
}
Swift Protocol referencing itself and associated type
You would look after something like this:
protocol Handler {
// ...
var next: some Handler<HandlerRequest == Self.HandlerRequest>? { get }
// ...
}
The problem here is that Swift doesn't (yet) have support for opaque return types that are protocols with associated types.
A solution to this limitation is to use a type eraser for the next
property:
protocol Handler {
associatedtype HandlerRequest
// shift the generic from a protocol with associated type to a generic struct
var next: AnyHandler<HandlerRequest>? { get }
func handle(_ request: HandlerRequest) -> LocalizedError?
}
struct AnyHandler<HandlerRequest> {
private var _handle: (HandlerRequest) -> LocalizedError?
private var _next: () -> AnyHandler<HandlerRequest>?
init<H: Handler>(_ handler: H) where H.HandlerRequest == HandlerRequest {
_next = { handler.next }
_handle = handler.handle
}
}
extension AnyHandler: Handler {
var next: AnyHandler<HandlerRequest>? { return _next() }
func handle(_ request: HandlerRequest) -> LocalizedError? {
return _handle(request)
}
}
This way you can benefit both the protocol, and having the next
property tied to the handler request type that you need.
As an added bonus for using protocols, you can still benefit the default implementation from the base class:
extension Handler {
func handle(_ request: HandlerRequest) -> LocalizedError? {
return next?.handle(request)
}
}
That's how cool protocols are in Swift, they allow you to avoid classes and use value types for as much as possible, by improving the concept of polymorphism.
Usage example:
struct LoginHandler: Handler {
var next: AnyHandler<AccountRequest>?
func handle(_ request: AccountRequest) -> LocalizedError? {
// do the login validation
}
}
struct SignupHandler: Handler {
var next: AnyHandler<AccountRequest>?
func handle(_ request: AccountRequest) -> LocalizedError? {
// do the signup validation
}
}
extension Handler {
// Helper function to easily create a type erased AnyHandler instance
func erase() -> AnyHandler<HandlerRequest> {
return AnyHandler(self)
}
}
// now let's put the handers to work:
let loginHandler = LoginHandler()
let signupHandler = SignupHandler(next: loginHandler.erase())
let someOtherAccountHandler = SomeOtherAccountHandler(next: signupHandler.erase())
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.
Why can we not cast to protocol types with associated types but achieve the same effect using generics?
Protocol-typed values are represented using an 'existential container' (see this great WWDC talk on them; or on Youtube), which consists of a value-buffer of fixed size in order to store the value (if the value size exceeds this, it'll heap allocate), a pointer to the protocol witness table in order to lookup method implementations and a pointer to the value witness table in order to manage the lifetime of the value.
Unspecialised generics use pretty much the same format (I go into this in slightly more depth in this Q&A) – when they're called, pointers to the protocol and value witness tables are passed to the function, and the value itself is stored locally inside the function using a value-buffer, which will heap allocate for values larger than that buffer.
Therefore, because of the sheer similarity in how these are implemented, we can draw the conclusion that not being able to talk in terms of protocols with associated types or Self
constraints outside of generics is just a current limitation of the language. There's no real technical reason why it's not possible, it just hasn't been implemented (yet).
Here's an excerpt from the Generics Manifesto on "Generalized existentials", which discusses how this could work in practice:
The restrictions on existential types came from an implementation
limitation, but it is reasonable to allow a value of protocol type
even when the protocol has Self constraints or associated types. For
example, considerIteratorProtocol
again and how it could be used as
an existential:protocol IteratorProtocol {
associatedtype Element
mutating func next() -> Element?
}
let it: IteratorProtocol = ...
it.next() // if this is permitted, it could return an "Any?", i.e., the existential that wraps the actual element
Additionally, it is reasonable to want to constrain the associated
types of an existential, e.g., "aSequence
whose element type is
String
" could be expressed by putting a where clause into
protocol<...>
orAny<...>
(per "Renamingprotocol<...>
toAny<...>
"):let strings: Any<Sequence where .Iterator.Element == String> = ["a", "b", "c"]
The leading
.
indicates that we're talking about the dynamic type,
i.e., theSelf
type that's conforming to theSequence
protocol.
There's no reason why we cannot support arbitrarywhere
clauses within
theAny<...>
.
And from being able to type a value as a protocol with an associated type, it's but a short step to allow for type-casting to that given type, and thus allow something like your first extension to compile.
Reuse associated type from protocol in another protocol
The requestType
associated type isn't necessary in the RequestProcessor
protocol, as it is implicit based on the requester
associated type.
You should just be able to define the RequestProcessor
protocol like this:
protocol RequestProcesser: AnyObject {
associatedtype requester: Requester
var request: requester { get set }
}
And use it like this:
class MyRequester: Requester {
typealias requestType = Int
var response: ((Int) -> ())?
}
class MyRequestProcessor: RequestProcesser {
typealias requester = MyRequester
var request = MyRequester()
}
Now, the response
closure parameter inside the process()
method will take on the associated type of the MyRequester
protocol (in this case Int
), so no casting is necessary.
Optional cast to protocol's associated type is failing (returning nil)
This was... just the debugger being a pile of rubbish! Decided to add in a print
statement to make absolutely sure it was nil
. Turns out that wasn't the case! Prints fine, but lldb
seems to think it's nil for whatever reason.
Related Topics
Padding a Swift String for Printing
Programmatically Navigate to New View in Swiftui
Swift Setter Causing Exc_Bad_Access
Swift Dictionary Default Value
How to Animate a Model's Rotation in Realitykit
How to Create Dictionary That Can Hold Anything in Key? or All the Possible Type It Capable to Hold
What's the Difference Between Aranchor and Anchorentity
Do Swift Inner Classes Have Access to Self of Outer Class
Add "For In" Support to Iterate Over Swift Custom Classes
How to Convert Timeinterval into Minutes, Seconds and Milliseconds in Swift
Swift: Orient Y-Axis Toward Another Point in 3-D Space
How to Save Data from Cloud Firestore to a Variable in Swift
Swift Imagepickercontroller Didfinishpickingmediawithinfo Not Fired
Sort Array by Calculated Distance in Swift
Why Non Optional Any Can Hold Nil
How to Cycle Through the Entire Alphabet with Swift While Assigning Values