作为学习的练习,我将在Swift中重写我的验证库。
我有一个ValidationRule协议定义了各个规则的外观:
ValidationRule
protocol ValidationRule { typealias InputType func validateInput(input: InputType) -> Bool //... }
关联的类型InputType定义要验证的输入的类型(例如,字符串)。它可以是显式的或通用的。
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) }
我以为这行得通,但是编译器不同意。
在下面的例子中,即使输入是一个字符串,rule1的InputType是一个字符串,并且rule2Ş InputType是一个String …
rule1
rule2
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)
在这里我们得到:
协议Foo只能用作通用约束,因为它具有Self或关联的类型要求。
我明白那个。我需要说的是这样的:
doSomethingWithFoos<F: Foo where F.FooType == F.FooType>(thing: [F]) { }
带有类型别名的协议不能以这种方式使用。Swift无法直接谈论诸如ValidationRule或的元类型Array。您只能处理类似ValidationRule where...或的实例Array<String>。使用typealiases,无法直接到达那里。因此,我们必须通过类型擦除间接到达那里。
Array
ValidationRule where...
Array<String>
Swift有几个类型擦除器。AnySequence,AnyGenerator,AnyForwardIndex,等等,这些都是协议的仿制版本。我们可以建立自己的AnyValidationRule:
AnySequence
AnyGenerator
AnyForwardIndex
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中,属性获取器不是正确的方法。因此,您需要额外的间接层validator。)
validator
validate
有了它,您可以制作所需的数组类型:
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<String>与仍然不同AnyValidationRule<Int>,因此将失败:
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