I have a protocol, Address
, which inherits from another protocol, Validator
, and Address
fulfills the Validator
requirement in the extension.
There is another protocol, FromRepresentable
, which has an associatedType
(ValueWrapper
) requirement which should be Validator
.
Now if I try to use Address
as associatedType
, then it does not compile. It says,
Inferred type 'Address' (by matching requirement 'valueForDetail') is invalid: does not conform to 'Validator'.
Is this usage illegal? Shouldn't we be able to use Address
in place of Validator
, as all Addresses
are Validator
.
Below is the piece of code I am trying.
enum ValidationResult {
case Success
case Failure(String)
}
protocol Validator {
func validate() -> ValidationResult
}
//Address inherits Validator
protocol Address: Validator {
var addressLine1: String {get set}
var city: String {get set}
var country: String {get set}
}
////Fulfill Validator protocol requirements in extension
extension Address {
func validate() -> ValidationResult {
if addressLine1.isEmpty {
return .Failure("Address can not be empty")
}
return .Success
}
}
protocol FormRepresentable {
associatedtype ValueWrapper: Validator
func valueForDetail(valueWrapper: ValueWrapper) -> String
}
// Shipping Address conforming to Address protocol.
// It should also implicitly conform to Validator since
// Address inherits from Validator?
struct ShippingAddress: Address {
var addressLine1 = "CA"
var city = "HYD"
var country = "India"
}
// While compiling, it says:
// Inferred type 'Address' (by matching requirement 'valueForDetail') is invalid: does not conform
// to 'Validator'.
// But Address confroms to Validator.
enum AddressFrom: Int, FormRepresentable {
case Address1
case City
case Country
func valueForDetail(valueWrapper: Address) -> String {
switch self {
case .Address1:
return valueWrapper.addressLine1
case .City:
return valueWrapper.city
case .Country:
return valueWrapper.country
}
}
}
Update: Filed a bug.
The problem, which David has already alluded to, is that once you constrain a protocol's associatedtype
to a specific (non @objc
) protocol, you have to use a concrete type to satisfy that requirement.
This is because protocols don't conform to themselves – therefore meaning that you cannot use Address
to satisfy the protocol's associated type requirement of a type that conforms to Validator
, as Address
is not a type that conforms to Validator
.
As I demonstrate in my answer here, consider the counter-example of:
protocol Validator {
init()
}
protocol Address : Validator {}
protocol FormRepresentable {
associatedtype ValueWrapper: Validator
}
extension FormRepresentable {
static func foo() {
// if ValueWrapper were allowed to be an Address or Validator,
// what instance should we be constructing here?
// we cannot create an instance of a protocol.
print(ValueWrapper.init())
}
}
// therefore, we cannot say:
enum AddressFrom : FormRepresentable {
typealias ValueWrapper = Address
}
The simplest solution would be to ditch the Validator
protocol constraint on your ValueWrapper
associated type, allowing you to use an abstract type in the method argument.
protocol FormRepresentable {
associatedtype ValueWrapper
func valueForDetail(valueWrapper: ValueWrapper) -> String
}
enum AddressFrom : Int, FormRepresentable {
// ...
func valueForDetail(valueWrapper: Address) -> String {
// ...
}
}
If you need the associated type constraint, and each AddressFrom
instance only expects a single concrete implementation of Address
as an input – you could use generics in order for your AddressFrom
to be initialised with a given concrete type of address to be used in your method.
protocol FormRepresentable {
associatedtype ValueWrapper : Validator
func valueForDetail(valueWrapper: ValueWrapper) -> String
}
enum AddressFrom<T : Address> : Int, FormRepresentable {
// ...
func valueForDetail(valueWrapper: T) -> String {
// ...
}
}
// replace ShippingAddress with whatever concrete type you want AddressFrom to use
let addressFrom = AddressFrom<ShippingAddress>.Address1
However, if you require both the associated type constraint and each AddressFrom
instance must be able to handle an input of any type of Address
– you'll have use a type erasure in order to wrap an arbitrary Address
in a concrete type.
protocol FormRepresentable {
associatedtype ValueWrapper : Validator
func valueForDetail(valueWrapper: ValueWrapper) -> String
}
struct AnyAddress : Address {
private var _base: Address
var addressLine1: String {
get {return _base.addressLine1}
set {_base.addressLine1 = newValue}
}
var country: String {
get {return _base.country}
set {_base.country = newValue}
}
var city: String {
get {return _base.city}
set {_base.city = newValue}
}
init(_ base: Address) {
_base = base
}
}
enum AddressFrom : Int, FormRepresentable {
// ...
func valueForDetail(valueWrapper: AnyAddress) -> String {
// ...
}
}
let addressFrom = AddressFrom.Address1
let address = ShippingAddress(addressLine1: "", city: "", country: "")
addressFrom.valueForDetail(AnyAddress(address))
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