In the following code, I want to test if x
is a SpecialController
. If it is, I want to get the currentValue
as a SpecialValue
. How do you do this? If not with a cast, then some other technique.
The last line there won't compile. There error is: Protocol "SpecialController" can only be used as a generic constraint because it has Self or associated type requirements.
protocol SpecialController {
associatedtype SpecialValueType : SpecialValue
var currentValue: SpecialValueType? { get }
}
...
var x: AnyObject = ...
if let sc = x as? SpecialController { // does not compile
What is an associated type? An associated type can be seen as a replacement of a specific type within a protocol definition. In other words: it's a placeholder name of a type to use until the protocol is adopted and the exact type is specified. This is best explained by a simple code example.
Type-erasure simply means "erasing" a specific type to a more abstract type in order to do something with the abstract type (like having an array of that abstract type). And this happens in Swift all the time, pretty much whenever you see the word "Any."
Protocol 'SomeProtocol' can only be used as a generic constraint because it has Self or associated type requirements. Code that uses a protocol that relies on associated types pays the price. Such code must be written using generic types. Generic types are also placeholders.
A Self requirement is a placeholder in a protocol for a type, used as part of the protocol, which is replaced by the concrete type that conforms to the protocol. A Self requirement can thus be thought of as a special case of an 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!")
}
[Edited to fix: : SpecialValue
, not = SpecialValue
]
This is not possible. SpecialValueController
is an "incomplete type" conceptually so the compiler cannot know. SpecialValueType
, although it is constrained by SpecialValue
, it is not known until it is determined by any adopting class. So it is a really placeholder with inadequate information. as?
-ness cannot be checked.
You could have a base class that adopts SpecialController
with a concrete type for SpecialValueController
, and have multiple child classes that inherit from the adopting class, if you're still seeking a degree of polymorphism.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With