основан на протокол-ориентированном дизайне с использованием Swift

Действительно простой метод в Swift, который использует преимущества протоколов, позволит вам практически без усилий имитировать внешний API вашего приложения. В текущем задании вы, безусловно, работаете над проектом, который подразумевает взаимодействие с внешним сервером, который передает данные в ваше приложение для iOS. Зависимость от этого внешнего ресурса создает возможность реализовать фиктивный аналог, поскольку мы фактически можем потратить большую часть времени на разработку, ориентируясь на фиктивный API, а не на реальный API. В настоящее время я стараюсь создать внутренний имитируемый объект API, живущий внутри моего приложения, чтобы иметь полный контроль над этой зависимостью при разработке приложения . Это дает мне очевидные преимущества:

  • Я могу продолжить работу, если API недоступен, поскольку мой макет API - это система с гарантированной 100% работоспособностью
  • Я больше не зависим от скорости сетевого уровня. На самом деле, я разрабатываю фиктивный API так, чтобы я мог подделать асинхронный запрос, используя временную задержку, но, если я не хочу знать что-то вроде того, как UIActivityIndicatorView при определенных условиях, я могу просто избавиться от задержки и ускорить свою работу. С другой стороны, я могу легко имитировать более длительные задержки, если мне нужно увидеть, как приложение работает при низкоскоростных интернет-соединениях, без необходимости использовать какие-либо взломы для ограничения моего интернет-соединения.
  • Я могу манипулировать данными, как мне заблагорассудится с легкостью. Даже если у меня есть среда разработки (т. Е. Запущен сервер на моем собственном компьютере) или есть доступ к удаленному промежуточному серверу, если мне просто нужно небольшое изменение в бэкэнде, чтобы проверить, как интерфейс iOS на это реагирует , мне нужно либо перейти в какой-то веб-интерфейс, чтобы изменить эти данные, либо, если он даже не существует, мне нужно перейти непосредственно к базе данных, чтобы внести изменения (в конечном итоге я мог бы даже использовать прокси, например Charles, для отредактируйте ответ, прежде чем он попадет в приложение). В любом случае, все эти подходы включают несколько шагов, выполнение которых может занять некоторое время, даже если я просто хочу изменить одно поле в json, которое я получаю из API. А что, если бы у меня был фиктивный объект API, который отвечает данными, присутствующими в локальном файле json? Это делает манипулирование данными так же просто, как редактирование одного файла, доступного из XCode
  • Я могу перешагнуть через любой ресурс, зависящий от сервера, поскольку я владею системой. Например, представьте себе приложение, которое всегда представляет экран входа в систему каждый раз, когда приложение запускается, вместо того, чтобы беспокоиться о вводе правильного имя пользователя и пароль, которые могли бы успешно аутентифицировать данного пользователя, почему бы просто не заставить фиктивный API отвечать успешно независимо от ввода? В конце концов, этот шаг аутентификации не является основной функцией функции, над которой я сейчас работаю, поэтому я просто хочу преодолеть это при запуске приложения в симуляторе.
  • Я получаю все бесплатно, когда приходит время писать свои модульные тесты (а мы все проводим модульное тестирование, не так ли?)

Что я получу от всего этого? Я экономлю время во время рабочего процесса, и это ускорение должно быть достаточной мотивацией для реализации фиктивного API, который живет «внутри» приложения iOS.

Имитировать API так же просто, как внести незначительные изменения в кодовую базу. Независимо от архитектуры, которую вы используете в своем приложении (MVC, MVVM, VIPER и т. Д.), Пока вы следуете базовому принципу разработки программного обеспечения, предусматривающему четкое разделение задач ( имея класс, который инкапсулирует все ваши обязанности API) все, что вам нужно сделать, это рефакторинг вашего кода, чтобы вы больше не полагались на этот конкретный тип класса API, а вместо этого развивались в направлении неизвестный тип, определенный протоколом.

Уровень API как протокол

В зависимости от архитектуры вашего приложения сетевой уровень наверняка каким-то образом изолирован: будь то класс со всем, что там есть, простая структура, содержащая ссылку на другие объекты, связанные с API, какая-то оболочка вокруг Alamofire или даже ваш собственный HTTP-клиент, вы, вероятно, имеете его как автономный объект, скрывающий сетевые данные от ваших UIViewController (кстати говоря, это не лучшая идея для UIViewController знать об уровне API, но это совершенно другая тема, выходящая за рамки этого поста). Теперь вместо использования класса или структуры вы можете использовать протокол, например:

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

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

Благодаря простому мышлению, ориентированному на протокол, мы только что наделили наше приложение способностью обходить все сетевые вызовы, которые будут заменены ложными данными, которыми мы владеем и контролируем.

Что касается того, как и когда на самом деле решить, какую конкретную реализацию использовать, я выбрал подход времени выполнения, где, в зависимости от наличия заданного аргумента запуска, переданного во время запуска приложения, приложение будет либо использовать та или иная реализация. Представьте, что я создаю экземпляр своего объекта API в моем UIViewController (что никогда не бывает хорошей идеей ... но, опять же, давайте оставим это за рамками этого обсуждения), я мог бы просто сделать что-то вроде этого:

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

Издевательство над данными

Когда дело доходит до имитации реальных данных, есть несколько вариантов. С одной стороны, мы можем просто создать нашу структуру, массив структур или любой другой тип, который замыкание принимает в качестве параметра, и просто вызвать это замыкание с этим параметром в реализации метода. С другой стороны, мы можем пойти дальше и включить утилиту, которая считывает данные из локального файла json и анализирует их до нужного типа, принятого тем же закрытием. На самом деле я предпочитаю этот способ, потому что я могу просто отбросить любой ответ, который ожидаю от реального API, в отдельный файл json и изменить значения по своему желанию. Это также легче читать, если структура данных в какой-то степени сложна, с множеством вложенных пар ключ-значение.

Важно помнить, что какой бы путь вы ни выбрали для имитации данных, у вас уже есть структура данных для них, такая же, как и в вашей реальной реализации API. Не имеет значения, используете ли вы структуру, соответствующую Codable, или что-то еще из внешней библиотеки, все, что вы используете для хранения данных в одной реализации протокола API, является точно такая же структура данных, которую вы будете использовать в другой реализации. И мы любим держать вещи СУХИМИ, не так ли?

Добавление аромата

В этом Mock API замечательно то, что в него очень просто добавить некоторые настройки, которые могут оказаться чрезвычайно полезными во время разработки. Неужели скорость вашей сети настолько высока, что вы не можете наблюдать за тем, как реализовали свой UIActivityIndicatorView? Что ж, давайте смоделируем что-то вроде 5-секундной задержки, чтобы у нас было достаточно времени, чтобы увидеть, как выглядит наш пользовательский интерфейс, когда приложение выполняет сетевой запрос:

Эта простая реализация просто применяет одну и ту же задержку к каждому запросу, реализованному в объекте MockAPI. В 90% случаев это ноль секунд, потому что, как я сказал в начале, я просто хочу ускорить рабочий процесс и не хочу, чтобы UIActivityIndicatorViews отображался при нажатии. Но не стесняйтесь проявить творческий подход к этому, вместо того, чтобы жестко кодировать его в исходном коде, почему бы не добавить пакет настроек в свои разработки, чтобы вы или даже менее технически подкованный член команды могли установить это значение из приложения настроек вместо этого?

Что насчет того, когда что-то не получается? Приложение выглядит и ведет себя должным образом, когда сервер возвращает ошибку? Правильно ли обрабатывается конкретная ошибка? Возможно, в конкретном случае использования есть отдельные сценарии, которые вы хотите обработать, и вы хотите имитировать каждый из них. Опять же, при использовании нашего Mock API ничего страшного:

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

Последние мысли

У меня есть образец проекта на Github, демонстрирующий некоторые из обсуждаемых здесь моментов. Это действительно простое приложение, которое подключается к Star Wars API или, если оно выполняется по правильной схеме, обходит это и использует реализацию Mock API. В этом случае вместо этого будут отображаться фильмы об инопланетянах на основе файла json, который поставляется с приложением. Пожалуйста, не обращайте внимания на архитектуру или супер-упрощенный интерфейс, это всего лишь пример приложения, посвященного обсуждаемой здесь теме.

Реализация Mock API в приложении для iOS - очень простая задача. Моя идея в этом посте состоит не в том, чтобы научить вас, как это делать, даже тот, кто плохо знаком с iOS и Swift, может легко придумать ту же концепцию, если этот человек разбирается в использовании преимуществ таких простых методов программирования, ориентированных на протоколы, подобных этой. Что наиболее важно, я хочу подчеркнуть ценность, которую я приобрел, когда начал издеваться над удаленным API в своих проектах: я мог забыть о бэкэнде, мне наплевать, реализовала ли бэкэнд-команда с учетом конечной точки или нет, мне не нужно было беспокоиться о знании и доступе к бэкэнд-стеку для управления данными, поскольку я мог делать это прямо в моем приложении. Позвольте мне еще раз подчеркнуть это: в настоящее время вряд ли существует приложение для iOS без внутреннего сервера, который передает данные в мобильное приложение и может создавать его полностью изолированным от своего существования. Это преимущество, которое ускорит ваш рабочий процесс.

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

Я надеюсь, что вы узнали о преимуществах, которые вы получаете при реализации фиктивного API, и если вы используете его в своем текущем проекте, просто сообщите, как это сработало для вас!