«В нашей компании особое внимание мы уделяем качеству выпускаемого продукта», – шаблонная фраза, которую часто можно увидеть на сайтах компаний, предлагающих различные услуги, и не только в сфере IT. Однако то, как достигается качество программного продукта, скрыто от пользователей и волнует их только в моменты, когда что-то пошло не так. Одним из качественных показателей работы приложения является то, как оно реагирует на нестандартные действия пользователя. Для большинства клиент-серверных решений эти действия связаны с вводом данных. Далее мы опишем, какие решения для проверки данных используются среди iOS разработчиков в нашей команде.

В мобильном приложении часто возникает типовая задача по валидации данных. Последствия, которые влечет ошибка на данном участке приложения, могут быть некритичными, а могут привести к замедлению работы всей системы в случае, если не были установлены ограничения на передаваемые данные. Не стоит забывать о специфике мобильных устройств: если приложение используется как мобильный клиент для отображения данных удаленного сервиса, то количество этих данных должно быть лимитировано, чтобы избежать их долгой загрузки.

Для начала выделим те основные данные, которые должны подвергаться проверке. Первый и самый распространенный вид проверки — это верификация введенных текстовых данных. Числа, как правило, тоже отображаются в текстовых полях и будут являться частным случаем текстового представления. Второй тип проверки должен быть связан с вводом даты. Часто приложению требуется проверять, является ли введенная дата раньше чем текущая. Третий тип, который должен проходить валидацию, это непосредственно «сырые» данные или, проще говоря, то количество байтов, с которым приложение работает без существенной потери в производительности.

Все перечисленные пункты и способы решения поставленных в них задач были собраны нами в небольшой фреймворк, названный по аналогии с другими стандартными iOS пакетами, — ValidationKit. Чтобы более ясно представлять, что включает данный фреймворк, его содержимое можно изобразить в виде следующей диаграммы:



Разными цветами обозначены логически обособленные части фреймворка, нацеленные на решение конкретной задачи из списка данных, подлежащих проверке. Сплошными линиями обозначено наследование, пунктирные линии означают реализацию протокола.
Итак, из диаграммы видно, что базовым протоколом для все классов валидаторов является <VLDValidator>. Производный от него протокол <VLDTextValidator> реализуют валидаторы, работающие непосредственно с текстовыми данными. Опишем вкратце, что представляет каждый из реализованных валидаторов:

VLDDateValidator — используется для валидации даты. По умолчанию, дата, переданная валидатору, не должна быть раньше текущей.

VLDRegExpValidator — используется как базовый класс для валидаторов, основанных на регулярном выражении. Для реализации собственного валидатора-потомка данного класса необходимо переопределить статический метод + (NSString *)regExpPattern, который возвращает строку-правило.

VLDEmailValidator, VLDStrongPasswordValidator, VLDPhoneNumberValidator — валидаторы на основе регулярного выражения, используются соответственно для проверки адреса электронной почты, защищенности пароля и номера телефона.

VLDBoundaryNumberValidator — используется для валидации числа в заданных границах. Указывается нижняя и верхняя граница попадающего под проверку числа. Например, указываемый год не может быть раньше 1900 и позже 2015.

VLDBoundaryCharactersValidator — используется для валидации количества символов. Указывается нижняя и верхняя граница количества символов. Например, телефон может быть не меньше 4 и не больше 11 цифр.

Стоит отметить, что в большом количестве проектов существует ограничение на длину обрабатываемых строк. Для того чтобы решить эту проблему, не перегружая код обработкой методов делегатов <UITextFieldDelegate> и <UITextViewDelegate>, были созданы классы VLDTextField и VLDTextView, которые не позволят ввести в поле символов больше, чем требуется. Также для этих классов можно задать кастомные валидаторы, реализующие протокол <VLDTextValidator>. Для VLDTextField также существует возможность на этапе инициализации задать один из видов валидации по умолчанию.

Задачей категорий NSString+VLDValidation и NSData+VLDValidation является проверка длины данных, используемых неявно для пользователя в приложении. Наиболее типичной ситуацией, когда это может потребоваться, является проверка размера данных перед передачей по сети удаленному сервису. Если, к примеру, картинка весит более 10 МБ, то это потребует существенного времени в условиях медленного интернета. Поэтому перед отправкой запроса на сервер можно удостовериться в том, что все строки и объекты данных не превышают заданных значений.

Также фреймворк по умолчанию использует логирование в консоль, которое можно отключить при помощи макроса, описанного в файле VLDHelpers.

В заключение стоит еще раз отметить те задачи, для решения которых был создан разработанный фреймворк:

  1. Ограничение символов, вводимых в поля для редактирования.
  2. Валидация email-а, пароля и телефонного номера.
  3. Создание кастомных валидаторов и использование их совместно с VLDTextField и VLDTextView.
  4. Проверка правильности даты.
  5. Проверка размера отправляемых или сохраняемых данных.

P.S. Исходный код проекта можно скачать тут.

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


  1. rule
    18.08.2015 01:59

    Почему вот эти два класса тоже не являются наследниками VLDRegExpValidator?:
    VLDBoundaryNumberValidator
    VLDBoundaryCharactersValidator

    Для того чтобы решить эту проблему, не перегружая код обработкой методов делегатов UITextFieldDelegate и UITextViewDelegate, были созданы классы VLDTextField и VLDTextView, которые не позволят ввести в поле символов больше, чем требуется.


    То-есть вы подменили стандартный эппловский шаблон делегирования, на наследование и считаете это хорошо? В Cocoa мире делегирование более предпочтительный подход, нежели наследование по ряду причин. В вашем случае точно лучше делегирование.
    Я вижу только одну причину наследования — это не позволять вводить символы — если не проходит валидацию ввод.


    1. Streetmage
      18.08.2015 11:11
      +1

      Попробую ответить на возникшие вопросы:

      1. Чтобы создать свой валидатор наследник от VLDRegExpValidator необходимо переопределить статический метод + (NSString *)regExpPattern. Как правило полей с разным количеством символов может быть много, поэтому создавать класс наследник под каждый случай не является хорошей идеей. Аналогичная ситуация и с валидацией числа. Допустим есть три отдельных поля с днем-месяцем-годом, то с текущей архитектурой создавать придется 3 класса наследника от VLDRegExpValidator, которые будут содержать лимиты чисел для дня-месяца-года.

      Тут сразу напрашивается вопрос о том, почему была выбрана именно такая архитектура, с переопределением статического метода. Почему нельзя было просто создать свойство, которое бы могло принять строку-паттерн? Есть случаи, когда разработчик называет объект, в нашем случае валидатор, одним именем с одним паттерном, а потом меняет регулярное выражение и начинает использовать валидатор в других целях. В данном случае такую неоднозначность было решено устранить описанным способом. Наиболее часто встречаемые ситуации, для проверки которых, как правило, используется регулярное выражение, были реализованы в классах потомках VLDRegExpValidator

      2. Первая причина, почему наследование было выбрано как инструмент расширения функциональности текстовых полей ввода, это удобство для разработчика в плане подключения фреймворка. То есть все сводится к замене названия класса и делегата и объявления правил проверки.

      Второй целью является интеграция созданных валидаторов с полями ввода.

      Третьей целью создания кастомных полей было ограничение вводимых символов. Как правило, если такое ограничение вводится на проекте согласно ТЗ, то разработчик реализует метод делегата — (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string. Чтобы избежать избыточного кода в контроллерах было решено перенести логику в класс наследник от текстового поля.

      Четвертой целью для использования наследования является то, что делегаты UITextFieldDelegate и UITextViewDelegate срабатывают только на действия пользователя. Но есть случаи, когда используется форма с автозаполнением полей. Таким образом в поле нельзя присвоить программно больше символов, чем разрешено.