无法在Swift中的另一个协议中使用协议作为关联类型
我有一个协议,地址
,它继承自另一个协议, Validator
和地址
满足扩展中的 Validator
要求。
I have a protocol, Address
, which inherits from another protocol, Validator
, and Address
fulfills the Validator
requirement in the extension.
还有另一个协议 FromRepresentable
,它有一个 associatedType
( ValueWrapper
)要求应为 Validator
。
There is another protocol, FromRepresentable
, which has an associatedType
(ValueWrapper
) requirement which should be Validator
.
现在如果我尝试使用地址
作为 associatedType
,然后它不编译。它说,
Now if I try to use Address
as associatedType
, then it does not compile. It says,
推断类型'地址'(通过匹配要求'valueForDetail')是
无效:不符合'验证者'。
Inferred type 'Address' (by matching requirement 'valueForDetail') is invalid: does not conform to 'Validator'.
这种用法是非法的吗?我们不应该使用地址
代替 Validator
,因为所有地址
是 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
}
}
}
更新:提交错误。
这个问题, David已经提到,是一旦你将协议的 associatedtype
约束到特定的(非 @objc
)协议,你有使用具体类型来满足该要求。
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.
这是因为协议不符合自己 - 因此意味着你不能使用地址
t o满足协议的相关类型要求,类型符合 Validator
,因为地址
不符合 Validator
的类型。
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
.
当我演示在我的回答中,考虑反例:
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
}
最简单的解决方案是放弃 ValueWrapper 关联类型的$ c> Validator 协议约束,允许您在方法参数中使用抽象类型。
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 {
// ...
}
}
如果您需要关联的类型约束,并且每个 AddressFrom
实例只需要地址
的单个具体实现作为输入 - 您可以使用泛型来为您的 AddressFrom
初始化,并使用在您的方法中使用的给定具体类型的地址。
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
但是,如果您同时需要关联的类型约束和每个 AddressFrom
实例必须能够处理任何类型的地址
的输入 - 你将拥有使用类型擦除以在具体类型中包装任意地址
。
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))