【问题标题】:Unable to use protocol as associatedtype in another protocol in Swift无法在 Swift 的另一个协议中使用协议作为关联类型
【发布时间】:2016-05-21 07:23:29
【问题描述】:

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

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

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

推断类型“地址”(通过匹配要求“valueForDetail”)是 无效:不符合“验证器”。

这种用法违法吗?难道我们不能使用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.

【问题讨论】:

    标签: ios swift swift-protocols associated-types


    【解决方案1】:

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

    这是因为protocols don't conform to themselves - 因此意味着您不能使用Address 来满足协议对符合Validator 的类型的关联类型要求,因为Address 不是符合Validator的类型。

    正如我演示的in my answer here,考虑以下反例:

    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
    }
    

    最简单的解决方案是放弃 Validator 关联类型上的 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))
    

    【讨论】:

    • 好吧,我明白了,但是我们有什么理由必须对带有协议约束的关联类型使用具体类型?如果我们使用 Address 作为需要 Validator 的函数的参数,编译器不会发出任何错误。
    • 我不明白,如果 associatedType 有协议约束,为什么我们必须使用具体类型。它与不变性行为或内存分配有关吗?你有什么参考资料,我可以在这方面提供更多信息。
    • @VishalSingh 恐怕我不能提供比“因为它就是这样”更好的理由——我当然想不出为什么它不应该是可能的,看到不受约束的版本工作正常。我似乎也无法在 swift Evolution 邮件列表或错误跟踪器上找到任何关于此的内容。可能值得提交bug report,看看他们对此有何评论。
    • 谢谢你,我会这样做的,如果我有任何东西会更新这篇文章。到那时,我会记住这一点:)
    • 很乐意提供帮助,我一定会对回复感兴趣 :)
    【解决方案2】:

    你有几个问题:

    首先,您实际上并没有声明 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
            }
        }
    }
    

    【讨论】:

    • 嘿,抱歉,我在尝试让它工作时发布了这个问题。我编辑了代码 sn-p。 Address 实际上继承自 Validator。关于 typealias,我认为它应该能够从 valueForDetail(_: Address) 函数中推断出 ValueWrapper 。我不希望它是具体类型。
    • 请注意,您实际上不必在实现中显式定义协议的associatedtype(通过typealias),Swift 可以从方法参数类型推断它。
    猜你喜欢
    • 1970-01-01
    • 2020-11-14
    • 1970-01-01
    • 2015-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-27
    相关资源
    最近更新 更多