GraphQL iOS: Использование облачных функций в приложении на Swift
В этой статье я показал вам, как использовать GraphQL с облачным кодом NodeJS в Back4app.
А в этом руководстве я показал вам, как использовать Apollo GraphQL iOS Client с помощью Swift.
Теперь давайте соберем все это вместе и сделаем множество сложных операций, создав действительно простой код. А еще давайте попросим XCode автоматически генерировать для нас любой код API, даже когда мы меняем классы.
Звучит неплохо? Так что берите кофе и вперед!
Contents
- 1 Прежде чем начать…
- 2 Несколько программ, которые нам помогут…
- 3 Создание классов
- 4 Принеси Облачный Код, детка!
- 5 Тестирование… Тестирование…
- 6 немного XCoding
- 7 Наш файл GraphQL
- 8 Немного Swift-кода
- 9 Запускаем!
- 10 Заключение
- 11 Какое программное обеспечение можно использовать для работы с облачными функциями в приложении на Swift?
- 12 В чем магия NPM?
Прежде чем начать…
Вам понадобится аккаунт Back4app с одним созданным приложением. Если у вас его еще нет, следуйте этому руководству, и у вас все получится в кратчайшие сроки.
Несколько программ, которые нам помогут…
Мы также будем использовать несколько вещей, чтобы ускорить работу.
Конечно, при желании вы можете сделать все вручную, но я не рекомендую этот путь, особенно новичкам.
Добавляя пакеты вручную, вам также придется вручную поддерживать пакеты, что через некоторое время может стать проблематичным.
Вам не обязательно использовать менеджеры пакетов, которые я продемонстрирую здесь, но я настоятельно рекомендую вам использовать хотя бы несколько менеджеров пакетов вместо того, чтобы заниматься ручным управлением. Как я уже говорил, ручное управление может стать причудливым со временем.
Я буду использовать Cocoapods для управления пакетами iOS и NPM для управления настольными системами, но, опять же, не стесняйтесь использовать то, что вам нравится.
Инструкции по установке Cocoapods можно найти на этом сайте.
Инструкции по установке NPM можно найти на этом сайте.
Создание классов
Мы будем реализовывать 3 класса: Владелец, Собака и Порода.
Наша модель будет предполагать, что:
- Владелец может иметь несколько собак
- Собака может иметь только одну породу
Нашими свойствами будут:
- Владелец
- имя (Строка)
- возраст (Число)
- адрес (Строка)
- hasDogs (Отношение к собаке, так как у владельца может быть несколько собак)
- Собака
- имя (Строка)
- день рождения (Дата)
- порода (Указатель на породу, так как у собаки есть только одна порода)
- Порода
- Название (Строка
Создайте эти классы графически или программно и заполните их данными. Мои классы выглядят следующим образом:
Порода
Собака
Владелец
Принеси Облачный Код, детка!
Пришло время для облачного кода. Но на этот раз я хочу углубиться в эту тему.
Я когда-нибудь говорил вам, что Cloud Code совместим с модулями NPM? Да, совместим! И это может сэкономить вам кучу времени!
Вы знаете, что вы хотели бы иметь крутую функциональность, но кто-то уже сделал это? Так вот, если это модуль NPM, вы можете использовать его!
В этом руководстве уже есть статья о том, как использовать модули, поэтому для этой реализации я буду использовать Moment, чтобы мы могли использовать день рождения собаки и вычислять ее возраст в месяцах.
Итак, мой файл package.json будет выглядеть следующим образом:
{ "dependencies": { "moment": "*" } }
* означает, что я буду использовать последнюю версию.
Мой код облака получился таким, как показано ниже. Он хорошо прокомментирован, поэтому я не буду вдаваться в подробности, но вы можете увидеть в первой же строке, что я использую модуль Moment NPM:
// Установка NPM-модуля Moment const moment = require('moment') Parse.Cloud.define("retrieveOwnersOrderedByAgeDescending", async req => { /* Эта функция извлекает всех владельцев в порядке убывания возраста. Если владельцев нет, будет получен пустой массив. */ const query = new Parse.Query("Owner"); query.descending("age") const results = await query.find(); return results; }); Parse.Cloud.define("retrieveAllGermanShepherds", async req => { /* Эта функция извлекает собак, имеющих породу "немецкая овчарка". Извлекаются все свойства собак. Если собак нет, будет получен пустой массив. */ const breedQuery = new Parse.Query("Порода"); breedQuery.equalTo("name", "German Shepherd") const query = new Parse.Query("Собака"); query.matchesQuery("порода", breedQuery); const results = await query.find(); return results; }); Parse.Cloud.define("retrieveDogIncludingBreedByName", async req => { /* Эта функция извлекает информацию о конкретной собаке, включая сведения о ее породе (название), по имени собаки. Извлекаются все свойства собак и пород. Если собак нет, извлекается null. */ const query = new Parse.Query("Dog"); query.equalTo("name", req.params.name); query.include("порода") const results = await query.find(); return results; }); Parse.Cloud.define("retrieveDogsNamesWithAgesInMonths", async req => { /* Эта функция извлекает имена собак со свойством даты рождения, преобразованным в месяцы с помощью модуля NPM. Извлекаются только имена собак, а возраст вычисляется в месяцах. Если собак нет, будет получен пустой массив. */ let dogs = []; const query = new Parse.Query("Dog"); const results = await query.find(); for (let i = 0; i < results.length; i ++){ // вычисление возраста в месяцах с помощью модуля NPM Moment let ageInMonths = moment.duration(moment().diff(results[i].get("birthday"))).humanize(); let newDog = { "dogName": results[i].get("name"), "dogAgeInMonths": ageInMonths } dogs.push(newDog); } return dogs; });
Мой файл schema.graphql, раскрывающий мои четыре метода, выглядит следующим образом:
extend type Query { getAllOwnersAgeDescending: [OwnerClass!]! @resolve(to: "retrieveOwnersOrderedByAgeDescending") getAllGermanShepherds: [DogClass!]! @resolve(to: "retrieveAllGermanShepherds") getThisDogWithBreed (name:String): [DogClass!]! @resolve(to: "retrieveDogIncludingBreedByName") getDogsAgesInMonths: [DogAge!]! @resolve(to: "retrieveDogsNamesWithAgesInMonths") } тип DogAge { dogName: String! dogAgeInMonths: String! }
Если вы не понимаете синтаксис, посмотрите эту статью в разделе “Небольшой дополнительный шаг”.
Обратите внимание на [DogAge!]! в getDogsAgesInMonths. Поскольку я буду получать объект, сгенерированный системой, у него не будет схемы для интерпретации GraphQL, поэтому я должен создать тип, чтобы наш клиент мог его интерпретировать.
Тестирование… Тестирование…
Время для тестирования!
Давайте перейдем в нашу консоль Parse GraphQL Console и запустим наши запросы, проверяя их результаты:
Запрос для getAllOwnersAgeDescending:
query{ getAllOwnersAgeDescending { имя возраст адрес } }
Запрос для getAllGermanShepherds:
запрос { getAllGermanShepherds { имя день рождения } }
Запрос для getThisDogWithBreed:
запрос{ getThisDogWithBreed(name: "Fido"){ имя день рождения порода{ имя } } }
Запрос для getDogsAgesInMonths:
query{ getDogsAgesInMonths }
Все работает! Отлично! Теперь пришло время…
немного XCoding
У нас есть все, что нужно от Back4app. Пришло время использовать это в XCode.
В этом руководстве было показано, как это сделать, но на этот раз мы немного продвинулись: у нас есть несколько методов, один из которых ожидает переменных, поэтому нам нужно внести несколько изменений.
Если вы еще не читали эту статью, я настоятельно рекомендую вам это сделать, так как она очень подробная.
Давайте начнем с создания приложения и установки клиента Apollo, как описано в статье.
После этого откройте файл Main.storyboard, нажмите кнопку Objects в правом верхнем углу и перетащите табличное представление в контроллер представления:
Измените размер табличного представления, чтобы оно соответствовало всему виду контроллера представлений, перетаскивая углы:
И создайте ограничения для всех четырех сторон, нажав кнопку Add New Constraints (Добавить новые ограничения) в правом нижнем углу экрана. Выделите четыре стороны (красные маркеры) и нажмите кнопку Add Constraints (Добавить ограничения), чтобы назначить эти новые ограничения:
Теперь, выбрав вид таблицы (щелкните по нему, чтобы выбрать), добавим ячейку прототипа. Щелкните Инспектор атрибутов в правом верхнем углу и замените 0 на 1 в поле Prototype Cells:
Теперь щелкните на только что созданной ячейке прототипа, чтобы выделить ее, и дайте ей хороший идентификатор: “cell”.
Мы будем использовать его позже в коде для идентификации этой ячейки.
Теперь нам нужно связать наш источник данных и делегат от представления таблицы с нашим контроллером представления.
Удерживая клавишу Control на клавиатуре, щелкните на представлении таблицы и перетащите его на желтый значок в верхней части контроллера представления, тот, на котором при наведении написано View Controller.
Если отпустить ссылку Table View, появится небольшое всплывающее окно. Выберите во всплывающем окне источник данных и делегат:
Итак, мы рассмотрели основные моменты, но нам все равно нужно будет вызвать обновление табличного представления после получения данных из Back4app. Для этого мы должны создать аутлет и подключить его к нашему пользовательскому интерфейсу.
Самый простой способ сделать это – нажать кнопку Show Assistant Editor в правом верхнем углу экрана, чтобы вы могли видеть бок о бок пользовательский интерфейс и код.
Затем щелкните мышью по таблице Table View и перетащите ее в код:
Когда вы отпустите его, появится всплывающее окно. Задайте ему имя и нажмите Connect:
После этого у вас должен появиться красивый подключенный Outlet (обратите внимание на маленький кружок, заполненный номером строки), и теперь мы можем вызывать код для этого Table View:
После этого мы можем перейти к нашему файлу ViewController.swift, потому что настало время для…
Наш файл GraphQL
В нашем руководстве по GraphQL было показано, как создать свой файл GraphQL. Для этого проекта мой файл выглядел следующим образом:
query findAllOwners{ getAllOwnersAgeDescending{ имя возраст адрес } } запрос findAllGermanShepherds{ getAllGermanShepherds { имя } } запрос findThisDog ($name: String!){ getThisDogWithBreed(name: $name){ имя день рождения порода{ имя } } } запрос agesInMonths{ getDogsAgesInMonths }
Немного Swift-кода
Вэтом руководстве мы уже рассказывали о том, как настроить Apollo в Swift, но в сегодняшнем коде есть несколько изменений.
Поскольку мы собираемся отображать данные в графическом виде на нашем Table View, мы должны включить два метода для нашего источника данных и делегата для выполнения. Вот эти методы
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
и
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
Я не буду много рассказывать об этом, так как они задокументированы в коде. Просто имейте в виду, что эти два метода являются обязательными для работы нашего Table View.
Ваш полный код должен выглядеть следующим образом:
// // ViewController.swift // GraphQLApollo // // Created by Venom on 19/08/19. // Copyright © 2019 Venom. Все права защищены. // импорт UIKit импорт Apollo класс ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet weak var tableView: UITableView! var list = [] as [String] // Этот массив будет содержать значения для отображения // Инициализация клиента Apollo. // Подробнее об этом здесь: https://www.back4app.com/docs/ios/swift-graphql let apollo: ApolloClient = { let configuration = URLSessionConfiguration.default configuration.httpAdditionalHeaders = [ "X-Parse-Application-Id": "lAcIOgR0ndd7R4uMqovhNAoudpi6tMsrOI9KCuyr", "X-Parse-Client-Key": "f1wwm6uWqQoxazys3QQrrtY4fKuQuxYvNYJmYQJP". ] let url = URL(string: "https://parseapi.back4app.com/graphql")! return ApolloClient( networkTransport: HTTPNetworkTransport( url: url, конфигурация: конфигурация ) ) }() override func viewDidLoad() { super.viewDidLoad() // Выполните дополнительную настройку после загрузки представления. apollo.fetch(query: FindAllOwnersQuery()) { result in guard let data = try? result.get().data else { return } for name in data.getAllOwnersAgeDescending { //Добавляем каждое имя в массив нашего списка self.list.append(name.name!) } //Строка ниже заставит перезагрузить наше представление таблицы после того, как мы получим данные DispatchQueue.main.async { self.tableView.reloadData() } } } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // Эта функция вызывается нашим табличным представлением через источник данных и делегат. // Она просто возвращает количество объектов для отображения в Table View. // Это обязательно для работы табличного представления. return list.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // Эта функция вызывается нашим представлением таблицы через источник данных и делегат. // Она создает ячейки для отображения с идентификатором, который мы задали на сториборде. // Она обязательна для работы табличного представления. let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell") cell.textLabel?.text = list[indexPath.row] return(cell) } }
Запускаем!
Пришло время запустить наш код! Нажимаем Command + R, и если все в порядке, вы должны увидеть наши имена в представлении таблицы:
Круто, да?
Теперь мы можем даже изменить наш код, чтобы выполнить другие методы GraphQL, и посмотреть, как это будет работать!
Давайте изменим наш GraphQL-запрос, чтобы выполнить наш метод getAllGermanShepherds:
override func viewDidLoad() { super.viewDidLoad() // Выполните дополнительную настройку после загрузки представления. apollo.fetch(query: FindAllGermanShepherdsQuery()) { result in guard let data = try? result.get().data else { return } print(data.getAllGermanShepherds) for name in data.getAllGermanShepherds { //Добавляем каждое имя в массив нашего списка self.list.append(name.name!) } //Строка ниже заставит перезагрузить наше представление таблицы после того, как мы получим данные DispatchQueue.main.async { self.tableView.reloadData() } } }
и результат…
А что, если мы хотим передать переменную в наш запрос?
override func viewDidLoad() { super.viewDidLoad() // Выполните дополнительную настройку после загрузки представления. apollo.fetch(query: FindThisDogQuery(name: "Fido")) { result in guard let data = try? result.get().data else { return } print(data.getThisDogWithBreed) for name in data.getThisDogWithBreed { //Добавляем каждое имя в массив нашего списка self.list.append(name.name! + " - " + (name.breed?.name!)! } //Строка ниже заставит перезагрузить наше представление таблицы после получения данных DispatchQueue.main.async { self.tableView.reloadData() } } }
И теперь мы знаем, что Фидо – это мопс:
А как насчет того метода, который возвращает новый тип? Давайте протестируем и его:
override func viewDidLoad() { super.viewDidLoad() // Выполняем дополнительную настройку после загрузки представления. apollo.fetch(query: AgesInMonthsQuery()) { result in guard let data = try? result.get().data else { return } print(data.getDogsAgesInMonths) for dog in data.getDogsAgesInMonths { //Добавляем каждое имя в массив нашего списка self.list.append(dog.dogName + " - " + dog.dogAgeInMonths) } //Строка ниже заставит перезагрузить наше представление таблицы после того, как мы получим данные DispatchQueue.main.async { self.tableView.reloadData() } } }
и результат:
Заключение
Облачный код – это круто. GraphQL – это суперкруто. Apollo привносит новый уровень крутизны в ваши нативные приложения для iOS. Все три компонента вместе дают вам серьезную возможность создавать очень сложные приложения с минимальными усилиями.
Теперь, когда вы можете создать полностью рабочий поток для приложения, который легко создавать и поддерживать в течение долгого времени, вопрос заключается в том, что вы собираетесь создавать дальше?
Надеюсь, вам понравилось! В ближайшее время мы подготовим еще больше материалов! Следите за новостями!
Какое программное обеспечение можно использовать для работы с облачными функциями в приложении на Swift?
Это будет зависеть от вашего собственного выбора. Вы должны использовать то программное обеспечение, которое вы можете использовать и которым легко управлять. Я использовал два ниже в приведенной выше практике.
-Cocoapods для управления пакетами IOS
-NPM для управления пакетами Desktop
В чем магия NPM?
Облачный код совместим с модулями NPM. Это отличная вещь. Это поможет вам сэкономить кучу времени. Это также даст вам преимущество перед вашими конкурентами. Так что совместимость NPM с облачными кодами даст вам импульс.