我有一个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
- // ...
- }
在其他地方,我有一个函数来验证带有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)
- }
我认为这会起作用,但编译器不同意.
在下面的示例中,即使输入是String,rule1的InputType也是String,而rule2s的InputType是String …
- 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"]))
- }
…我收到了非常有用的错误消息:
_ is not convertible to 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 can only be used as a generic constraint because it has
Self or associated type requirements.
我明白那个.我需要说的是:
- doSomethingWithFoos<F: Foo where F.FooType == F.FooType>(thing: [F]) {
- }
解决方法
Swift有几种类型的擦除器. 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) }
- }
这里的深刻魔法是验证者.有可能还有其他方法可以在没有闭包的情况下进行类型擦除,但这是我所知道的最佳方式. (我也讨厌Swift无法处理validate作为闭包属性这一事实.在Swift中,属性getter不是正确的方法.所以你需要额外的验证器间接层.)
有了这个,你可以制作你想要的那种阵列:
- 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<字符串>仍然与AnyValidationRule< Int>不同,所以这将失败:
- let len = ValidationRuleLength()
- let condInt = ValidationRuleCondition<Int>()
- let badRules = [AnyValidationRule(len),AnyValidationRule(condInt)]
- // error: type of expression is ambiguous without more context