【问题标题】:How do I add different types conforming to a protocol with an associated type to a collection?如何将符合具有关联类型的协议的不同类型添加到集合中?
【发布时间】:2015-10-24 01:40:37
【问题描述】:

作为学习练习,我正在用 Swift 重写我的 validation library

我有一个ValidationRule 协议,它定义了各个规则的外观:

protocol ValidationRule {
    typealias InputType
    func validateInput(input: InputType) -> Bool
    //...
}

关联类型InputType 定义要验证的输入类型(例如字符串)。它可以是显式的或通用的。

这里有两条规则:

struct ValidationRuleLength: ValidationRule {
    typealias InputType = String
    //...
}

struct ValidationRuleCondition<T>: ValidationRule {   
    typealias InputType = T
    // ...
}

在其他地方,我有一个函数可以使用 ValidationRules 的集合验证输入:

static func validate<R: ValidationRule>(input i: R.InputType, rules rs: [R]) -> ValidationResult {
    let errors = rs.filter { !$0.validateInput(i) }.map { $0.failureMessage }
    return errors.isEmpty ? .Valid : .Invalid(errors)
}

我认为这会起作用,但编译器不同意。

在以下示例中,即使输入是字符串,rule1InputType 也是字符串,而 rule2s InputType 是字符串...

func testThatItCanEvaluateMultipleRules() {

    let rule1 = ValidationRuleCondition<String>(failureMessage: "message1") { $0.characters.count > 0 }
    let rule2 = ValidationRuleLength(min: 1, failureMessage: "message2")

    let invalid = Validator.validate(input: "", rules: [rule1, rule2])
    XCTAssertEqual(invalid, .Invalid(["message1", "message2"]))

}

...我收到了非常有用的错误消息:

_ 不能转换为 ValidationRuleLength

这很神秘,但表明类型应该完全相等?

所以我的问题是......我如何将所有符合具有关联类型的协议的不同类型附加到集合中?

不确定如何实现我正在尝试的目标,或者是否有可能?

编辑

这是没有上下文的:

protocol Foo {
    typealias FooType
    func doSomething(thing: FooType)
}

class Bar<T>: Foo {
    typealias FooType = T
    func doSomething(thing: T) {
        print(thing)
    }
}

class Baz: Foo {
    typealias FooType = String
    func doSomething(thing: String) {
        print(thing)
    }
}

func doSomethingWithFoos<F: Foo>(thing: [F]) {
    print(thing)
}

let bar = Bar<String>()
let baz = Baz()
let foos: [Foo] = [bar, baz]

doSomethingWithFoos(foos)

我们得到:

Protocol Foo 只能用作通用约束,因为它具有 自身或关联的类型要求。

我明白这一点。我需要说的是:

doSomethingWithFoos<F: Foo where F.FooType == F.FooType>(thing: [F]) {

}

【问题讨论】:

  • 你使用的是什么版本的 Swift?
  • 我正在使用最新的 Swift 2
  • 什么是验证结果?请提供足够的代码以便我重现
  • 好的,添加了一个通用的等价物

标签: ios swift generics protocols type-alias


【解决方案1】:

不能以这种方式使用具有类型别名的协议。 Swift 没有办法直接谈论像 ValidationRuleArray 这样的元类型。您只能处理像ValidationRule where...Array&lt;String&gt; 这样的实例化。使用 typealiases,没有办法直接到达那里。所以我们必须通过类型擦除间接到达那里。

Swift 有几个类型擦除器。 AnySequenceAnyGeneratorAnyForwardIndex 等。这些是协议的通用版本。我们可以建立自己的AnyValidationRule

struct AnyValidationRule<InputType>: ValidationRule {
    private let validator: (InputType) -> Bool
    init<Base: ValidationRule where Base.InputType == InputType>(_ base: Base) {
        validator = base.validate
    }
    func validate(input: InputType) -> Bool { return validator(input) }
}

这里的魔法是validator。可能有其他方法可以在没有闭包的情况下进行类型擦除,但这是我所知道的最好的方法。 (我也讨厌 Swift 无法处理 validate 作为闭包属性的事实。在 Swift 中,属性 getter 不是正确的方法。所以你需要 validator 的额外间接层。)

有了这些,你就可以制作你想要的数组了:

let len = ValidationRuleLength()
len.validate("stuff")

let cond = ValidationRuleCondition<String>()
cond.validate("otherstuff")

let rules = [AnyValidationRule(len), AnyValidationRule(cond)]
let passed = rules.reduce(true) { $0 && $1.validate("combined") }

请注意,类型擦除不会抛弃类型安全。它只是“擦除”了一层实现细节。 AnyValidationRule&lt;String&gt; 仍然与 AnyValidationRule&lt;Int&gt; 不同,所以这会失败:

let len = ValidationRuleLength()
let condInt = ValidationRuleCondition<Int>()
let badRules = [AnyValidationRule(len), AnyValidationRule(condInt)]
// error: type of expression is ambiguous without more context

【讨论】:

  • 这是一个很好的答案,谢谢。我想我以前从未见过这样的事情。你从哪里学来的?有兴趣阅读更多内容。
  • 我最喜欢的来源是 Swift 头文件本身。那里有大量的文档,如果你仔细阅读的话,包括很多“为什么”。我花了很多时间尝试构建奇怪的数据结构并在 Twitter 上抱怨它们不起作用并让 @jckarter 纠正我。 airspeedvelocity.net 也是我感兴趣的主题的一个很好的来源。
  • 当你想调用 validate 时,你为什么需要闭包并且不能只存储“base”并在存储的 base 上调用 validate?
  • @JPC 最好的理解方法是尝试实现它。您几乎会立即找到墙壁。
猜你喜欢
  • 1970-01-01
  • 2020-05-24
  • 2019-11-23
  • 1970-01-01
  • 1970-01-01
  • 2015-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多