I want to store a collection of protocols with different values for it's associated type. I believe a type erased wrapper would be required, which i've attempted to implement below. After reading numerous articles I believe this is not possible due to the lack of covariance support (please correct me if I'm wrong).
In any case given the current set of features in swift, how would you represent a collection of AnyValidator? Is this possible?
I'm not bound to using protocols, this is just an example of what I'm trying to accomplish.
Thank you!
import Foundation
protocol Validator {
associatedtype Value
func isValid(_ value: Value?) -> Bool
}
class AnyValidator<T>: Validator {
private let _isValid: (T?) -> Bool
init<U: Validator>(_ validator: U) where U.Value == T {
_isValid = validator.isValid
}
func isValid(_ value: T?) -> Bool {
return _isValid(value)
}
}
struct RequiredStringValidator: Validator {
func isValid(_ value: String?) -> Bool {
if let string = value?.trimmingCharacters(in: .whitespaces), !string.isEmpty {
return true
} else {
return false
}
}
}
struct DataValidator: Validator {
func isValid(_ value: Data?) -> Bool {
if let data = value, !data.isEmpty {
return true
} else {
return false
}
}
}
var validators = [AnyValidator<Any>]()
// cannot convert value of type 'AnyValidator<String>' to expected argument 'AnyValidator<Any>'
validators.append(AnyValidator(RequiredStringValidator()))
// cannot convert value of type 'AnyValidator<Data>' to expected argument 'AnyValidator<Any>'
validators.append(AnyValidator(DataValidator()))
1 Like
Karl
(👑🦆)
2
You could store them in an Array by boxing them in Any existentials and dynamically downcasting on retrieval. It might be still be useful to wrap them in AnyValidator so you don’t need to cast to every possible validator.
1 Like
Hi Karl thanks for the response. Is this what you are suggesting for the first solution? It does indeed solve the original question, but I didn't communicate clearly what my intent was.
let validators: [Any] = [RequiredStringValidator(), DataValidator()]
for validator in validators {
switch validator {
case let stringValidator as RequiredStringValidator:
break
case let dataValidator as DataValidator:
break
default:
break
}
}
I would like to keep the array type safe in some way if possible. Some background on the problem at hand is that I'd like to store a collection of validators on a field UIControl. But the subclass of UIControl would ideally have a signature of:
class Field: UIControl {
var validators = [AnyValidator]()
var isValid: Bool { ... }
}
I'm not quite following the second part in regards to wrapping them in AnyValidator. Since there is a generic argument how would you represent them in an array with different validators?
Thanks,
Jim
Karl
(👑🦆)
4
Sorry for the delay. Okay, in your case I think the best thing would be to make Field generic:
class Field<DataFormat>: UIControl {
var validators = [AnyValidator<DataFormat>]()
var currentValue: DataFormat?
var isValid: Bool {
return validators.first { !$0.isValid(currentValue) } == nil
}
}
class Switch: Field<Bool> { /* ... */ }
class TextField: Field<String> { /* ... */ }
Also, as an aside: I would rather flip the predicate to be isInvalid (it's like the difference between innocent and not-guilty).
1 Like