ios swift swift-protocols associated-types

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 } } }