ios - No se puede usar el protocolo como tipo asociado en otro protocolo en Swift
swift-protocols associated-types (2)
Tengo un protocolo,
Address
, que hereda de otro protocolo,
Validator
, y
Address
cumple el requisito de
Validator
en la extensión.
Hay otro protocolo,
FromRepresentable
, que tiene un requisito de tipo
associatedType
(
ValueWrapper
) que debería ser
Validator
.
Ahora, si trato de usar la
Address
como tipo
associatedType
, entonces no se compila.
Dice,
El tipo inferido ''Dirección'' (al hacer coincidir el requisito ''valueForDetail'') no es válido: no se ajusta al ''Validador''.
¿Es ilegal este uso?
¿No deberíamos poder usar la
Address
en lugar del
Validator
, ya que todas las
Addresses
son
Validator
?
A continuación se muestra el código que estoy intentando.
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
}
}
}
Actualización: Archivado un bug.
El problema, al que
David ya ha aludido
, es que una vez que restringe el tipo
associatedtype
un protocolo a un protocolo específico (no
@objc
), debe usar un tipo concreto para satisfacer ese requisito.
Esto se debe a que los
protocolos no se ajustan a sí mismos
, lo que significa que no puede usar la
Address
para satisfacer el requisito de tipo asociado del protocolo de un tipo que se ajusta al
Validator
, ya que la
Address
no
es un tipo que se ajusta al
Validator
.
Como lo demuestro en mi respuesta aquí , considere el contraejemplo de:
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
}
La solución más simple sería
ValueWrapper
restricción del protocolo
Validator
en su tipo asociado
ValueWrapper
, permitiéndole usar un tipo abstracto en el argumento del método.
protocol FormRepresentable {
associatedtype ValueWrapper
func valueForDetail(valueWrapper: ValueWrapper) -> String
}
enum AddressFrom : Int, FormRepresentable {
// ...
func valueForDetail(valueWrapper: Address) -> String {
// ...
}
}
Si necesita la restricción de tipo asociada, y cada instancia de
AddressFrom
solo espera una única implementación concreta de
Address
como entrada, puede usar genéricos para que su
AddressFrom
se inicialice con un tipo concreto de dirección que se utilizará en su método.
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
Sin embargo, si necesita la restricción de tipo asociada
y
cada instancia de
AddressFrom
debe ser capaz de manejar una entrada de cualquier tipo de
Address
, deberá usar un borrado de tipo para envolver una
Address
arbitraria en un tipo concreto.
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))
Tienes varios problemas:
En primer lugar, en realidad no declaras que Address implementa Validator
//Address inherits Validator
protocol Address : Validator {
var addressLine1: String {get set}
var city: String {get set}
var country: String {get set}
}
Y no declaras el tipo asociado para ValueWrapper:
typealias ValueWrapper = ShippingAddress
Y parece que realmente deseas que AddressFrom.valueForDetail tome una dirección de
ShippingAddress
:
func valueForDetail(valueWrapper: ShippingAddress) -> String {
switch self {
case .Address1:
return valueWrapper.addressLine1
case .City:
return valueWrapper.city
case .Country:
return valueWrapper.country
}
}
En total, se ve así:
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 implicity 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
// define associated type for FormRepresentable
typealias ValueWrapper = ShippingAddress
func valueForDetail(valueWrapper: ShippingAddress) -> String {
switch self {
case .Address1:
return valueWrapper.addressLine1
case .City:
return valueWrapper.city
case .Country:
return valueWrapper.country
}
}
}