Collections of protocols with associated types

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

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

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