首页 文章

如何将符合具有关联类型的协议的不同类型添加到集合中?

提问于
浏览
6

作为一个学习练习,我在Swift中重写了我的validation library .

我有一个 ValidationRule 协议,它定义了各个规则应该是什么样子:

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

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

这有两条规则:

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

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

在其他地方,我有一个函数来验证带有 ValidationRule 集合的输入:

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 是字符串, rule2 s 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

哪个是神秘的,但暗示类型应该完全相同?

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

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

EDIT

这是没有上下文的:

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只能用作通用约束,因为它具有Self或相关类型要求 .

我明白那个 . 我需要说的是:

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

}

1 回答

  • 12

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

    Swift有几种类型的擦除器 . AnySequenceAnyGeneratorAnyForwardIndex 等 . 这些是协议的通用版本 . 我们可以 Build 自己的 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") }
    

    请注意,类型擦除不会丢弃类型安全 . 它只是"erases"一层实现细节 . AnyValidationRule<String> 仍与 AnyValidationRule<Int> 不同,因此会失败:

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

相关问题