无法在 Swift 的另一个协议中将协议用作关联类型

Unable to use protocol as associatedtype in another protocol in Swift

我有一个协议 Address,它继承自另一个协议 Validator,并且 Address 满足扩展中的 Validator 要求。

还有另一个协议 FromRepresentable,它有一个 associatedType (ValueWrapper) 要求,应该是 Validator

现在,如果我尝试将 Address 用作 associatedType,则它不会编译。它说,

Inferred type 'Address' (by matching requirement 'valueForDetail') is invalid: does not conform to 'Validator'.

这种用法是否违法?我们不应该使用 Address 代替 Validator,因为所有 Addresses 都是 Validator

下面是我正在尝试的代码。

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

更新: 提交了 bug.

您有几个问题:

首先,你实际上并没有声明Address实现了Validator

//Address inherits Validator
protocol Address : Validator {
    var addressLine1: String {get set}
    var city: String {get set}
    var country: String {get set}
}

并且您没有为 ValueWrapper 声明关联类型:

typealias ValueWrapper = ShippingAddress

你似乎真的想要 AddressFrom.valueForDetail 参加 ShippingAddress:

func valueForDetail(valueWrapper: ShippingAddress) -> String {
    switch self {
    case .Address1:
        return valueWrapper.addressLine1
    case .City:
        return valueWrapper.city
    case .Country:
        return valueWrapper.country
    }
}

总的来说,它看起来像:

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

的问题在于,一旦将协议的 associatedtype 限制为特定(非 @objc)协议,就必须使用具体类型来满足要求。

这是因为——因此意味着你不能使用Address来满足协议对符合Validator的类型的关联类型要求,因为Address 不是符合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 关联类型上的 Validator 协议约束,允许您在方法参数中使用抽象类型。

protocol FormRepresentable {
    associatedtype ValueWrapper
    func valueForDetail(valueWrapper: ValueWrapper) -> String
}

enum AddressFrom : Int, FormRepresentable {

    // ...

    func valueForDetail(valueWrapper: Address) -> String {
        // ...
    }
}

如果你需要关联的类型约束,并且每个 AddressFrom 实例只需要一个 Address 的具体实现作为输入——你可以使用泛型来实现你的 AddressFrom使用要在您的方法中使用的给定具体地址类型进行初始化。

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 实例必须能够处理任何类型的 Address –您将使用类型擦除来将任意 Address 包装在具体类型中。

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