Всем доброго времени суток. Сегодня хочется поговорить про проблему валидации строк в 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
}

Для начала были реализованы проверки на Длину, Регистры. Выглядит это примерно так:

LowercaseLetterExistCriteria

public struct LowercaseLetterExistCriteria : Criteriable {
    public var debugErrorString: String = debugMessage(LowercaseLetterExistCriteria.self, message:"no lowercase char exists")
    
    public init(){}

    public func isСonform(to value: String)  -> Bool {
        for char in value.characters {
            if char.isLowercase() == true {
                return true
            }
        }
        return false
    }
    
}



NumberExistCriteria

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 {
        let regExptest = NSPredicate(format: "SELF MATCHES %@", ".*[0-9]+.*")
        return regExptest.evaluate(with: value)
    }
}


UppercaseLetterExistCriteria

public struct UppercaseLetterExistCriteria : Criteriable {
    public var debugErrorString: String = debugMessage(UppercaseLetterExistCriteria.self, message:"no uppercase char exists")
    
    public init(){}
    
    public func isСonform(to value: String) -> Bool {
        for char in value.characters {
            if char.isUppercase() == true {
                return true
            }
        }
        return false
    }
}


Затем был реализован сам валидатор:

StringValidator

/// 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.
Так же есть идея расширить функционал на проверку не только строк, а любых обьектов.

Надеюсь данный материал будет полезен.
Спасибо за внимание.
Поделиться с друзьями
-->

Комментарии (7)


  1. FirsofMaxim
    24.11.2016 11:10

    Спасибо! Здесь реализован похожий фукнционал на extensions, на мой взгляд это удобнее.


    1. krizhanovskii
      24.11.2016 11:35

      это не совсем то, т.к. в данном виде, тут просто вынесена реализация проверки в разшерение протокола. Она жестко определена, каким критериям должен соответствовать, например, логин. У меня же идея была реализовать валидатор, что бы эти критерии задавались на лету, какие нужны в данный момент.


  1. greyfruit
    24.11.2016 13:40

    Полезно, буду юзать ;)


  1. Sirikid
    24.11.2016 13:56
    +1

    То есть что бы проверить что в строке есть хотя бы один символ в нижнем регистре вы берете каждый символ, приводите его к строке, её переводите в нижний регистр и сравниваете с так же созданной строкой и проверяете что сивол не цифра пытаясь привести строку из него к типу Int?

    Оффтопик
    > — Минимум одна цифра
    > — Буквы верхнего и нижнего регистра
    совершенно бесполезные требования


    1. krizhanovskii
      24.11.2016 14:06

      Оффтопик

      Почему же бесполезные, создавая пароль, например к Apple ID, Вы обязаны будете выполнить такие требования


      1. Sirikid
        24.11.2016 14:28

        Потому что злоумышленник тоже знает что там должна быть минимум одна цифра и буквы в верхнем и нижнем регистре, больше ограничений = меньше энтропии. Это единственный вопрос который у вас возник, куча создаваемых строк вас не смущают?
        http://swiftlang.ng.bluemix.net/#/repl/5836cd2c27a1b233cd0fa970
        (Первые две функции тоже надо переписать на index(where:))


        1. krizhanovskii
          24.11.2016 14:43

          Спасибо, согласен с Вами на счет излишка создания строк. Это будет переделано.