Всем доброго времени суток. Сегодня хочется поговорить про проблему валидации строк в IOS проектах. Думаю Вы как и я часто с этим сталкиваетесь, когда надо проверить, например, поле пароля на соответствие нескольким критериям.
Например:
— Длина пароля больше 6 символов
— Минимум одна цифра
— Буквы верхнего и нижнего регистра
Зачастую такое требование реализовываются примерно так:
func isPasswordCorrect(_ value:String) -> Bool {
// code for check length, number exist, uppercase and lowercase chars
}
Просто. Функция работает, пароль проверяется. Все довольны.
Дальше если нам надо проверить поле email на корректность, мы также пишем функцию, например:
func isEmailCorrect(_ value:String) -> Bool {
// code for check length, number exist, uppercase and lowercase chars
}
И так далее.
По росту проекта функций с такими проверками становится все больше и больше. При создании нового проекта нам надо или начинать все сначала или копировать эти функции с прошлого проекта. Не очень удобно. Один из вариантов решения под катом.
В один момент я понял что пора решать эту проблему.
Очевидным решением было написать свой Валидатор.
По правде, ничего нового я не придумывал, подобные валидаторы уже существую и их легко найти на github. Но цель была написать именно свой, возможно лучше чем уже представленые
Главными задачами были:
— Универсальный способ, вызывать с любого места, тут же получаем результат
— Легкая настройка, быстро указываем критерии проверки
— Масштабируемость.
Первым шагом было определить как мы будет создавать критерии. Для этого был создан протокол:
public protocol Criteriable {
/// debug string for debug description of problem
var debugErrorString : String {get}
/// Check if value conform to criteria
///
/// - Parameter value: value to be checked
/// - Returns: return true if conform
func isConform(to value:String) -> Bool
}
Для начала были реализованы проверки на Длину, Регистры. Выглядит это примерно так:
public struct LowercaseLetterExistCriteria : Criteriable {
public var debugErrorString: String = debugMessage(LowercaseLetterExistCriteria.self, message:"no lowwercase char exist")
public init(){}
public func isСonform(to value: String) -> Bool {
for char in value.characters {
if String(char).lowercased() == String(char) && Int(String(char)) == nil {
return true
}
}
return false
}
}
public struct NumberExistCriteria : Criteriable {
public var debugErrorString: String = debugMessage(NumberExistCriteria.self, message:"no number char exist")
public init(){}
public func isСonform(to value: String) -> Bool {
for char in value.characters {
if let _ = Int(String(char)) {
return true
}
}
return false
}
}
public struct UppercaseLetterExistCriteria : Criteriable {
public var debugErrorString: String = debugMessage(UppercaseLetterExistCriteria.self, message:"no uppercase char exist")
public init(){}
public func isСonform(to value: String) -> Bool {
for char in value.characters {
if String(char).uppercased() == String(char) && Int(String(char)) == nil {
return true
}
}
return false
}
}
Затем был реализован сам валидатор:
/// Validator
public struct StringValidator {
/// predictions
public var criterias: [Criteriable]
///init
public init(_ criterias: [Criteriable]) {
self.criterias = criterias
}
/// validate redictors to comform
///
/// - Parameters:
/// - value: string than must be validate
/// - forceExit: if true -> stop process when first validation fail. else create array of fail criterias
/// - result: result of validating
public func isValide(_ value:String, forceExit:Bool, result:@escaping (ValidatorResult) -> ()) {
// validating code
}
}
Валидатор инициализируется набором критериев, устанавливается флаг (собирать все критерии которые не пройдены или обрываться при первом найденом критерии) и передается строка которая будет отвалидирована. Результатом получаем перечисляемый тип:
/// Validator result object
///
/// - valid: everething if ok
/// - notValid: find not valid criteria
/// - notValide: not valid array of criterias
public enum ValidatorResult {
case valid
case notValid(criteria:Criteriable)
case notValides(criterias:[Criteriable])
}
Также для масштабируемости легко можно определить собственный критерий:
struct MyCustomCriteria : Criteriable {
var debugErrorString: String = debugMessage(MyCustomCriteria.self, message:"some debug message")
func isConform(to value: String) -> Bool {
/* some logic for check */
return false
}
}
В итоге мы получили нужный нам функционал, не нужно больше плодить функции. Достаточно определить набор критериев и проверять строку по ним.
Так же был создан CocoaPod.
Заключение
В будущем есть планы расширить функционал валидатора на моментальную проверку UITextField.
Так же есть идея расширить функционал на проверку не только строк, а любых обьектов.
Надеюсь данный материал будет полезен.
Спасибо за внимание.
Автор: krizhanovskii