Как проще всего выполнять запросы GraphQL в iOS

Такие сторонние библиотеки, как Apollo iOS client и AWS AppSync нужны для связи с сервером GraphQL в iOS-приложении, но они также вводят в ваш проект лишний код.

Клиент Apollo iOS нормализует результаты запросов для построения клиентского кэша с помощью базы данных SQLite. АWS AppSync снабжает клиент Apollo некоторыми дополнительными функциями для выполнения проверки подлинности AWS.

Я всегда чувствую себя неуверенно, когда связываю свой проект со сторонними библиотеками.

Поводы для беспокойства здесь следующие:

  • Насколько безопасна библиотека?
  • Что если библиотеку перестанут поддерживать?
  • Сколько времени уходит у команды поддержки на то, чтобы исправить ошибку и выпустить патч?
  • Кто же сообщает клиентам, что не может исправить ошибку по вине третьей стороны?

По счастью, есть простой и легкий способ выполнять GraphQL с помощью необработанного HTTP-запроса.

В этой статье продемонстрирую его пошаговую настройку (на основе Star War GraphQL API).

1.Операция query

В GraphQL есть три типа операций: query, mutation и subscription.

Запрос query извлекает данные из API. Это можно выполнить HTTP-методом GET или POST в зависимости от API GraphQL.

Для API Star War GraphQL применяется метод POST.

Далее приведен пример формы для стандартного POST-запроса GraphQL. Тип содержимого в этом запросе  —  application/json. А форма  —  это его тело, закодированное в JSON-формате.

{
  "query": "...",
  "variables": { "myVariable": "someValue", ... }
}

Начнем со строки query.

2.Строка query

В теле запроса это выглядит так:

let query: String =
    """
    query allFilms($filter: FilmFilter) {
      allFilms(filter: $filter) {
        title
        director
        starships {
          pilots {
            name
          }
        }
      }
    }
    """
  • Здесь определяется строка запроса на получение фильмов с помощью фильтра.
  • Ключевое слово query в строке определяет тип операции, которая будет выполнена.
  • allFilms  —  это название операции. Оно не обязательно, но значимое и недвусмысленное имя помогает идентифицировать операцию.
  • $filter  —  это одна из переменных, принимаемых запросом, за которой следует тип фильтра Filter. Можно также передать другую переменную, а не создавать ее в запросе.
  • Блок fields описывает конкретные поля, которые мы запрашиваем, и имеет ту же форму, что и то, что мы получаем из API GraphQL.
{
  title
  director 
  starships {
    pilots {
      name
    }
  }
}

3.Кодируемые переменные

Как сказано в документации Star War GraphQL API, дополнения запроса allFilms таковы:

allFilms(
filter: FilmFilter
orderBy: FilmOrderBy
skip: Int
after: String
before: String
first: Int
last: Int
): [Film!]!

Они полностью опциональны. Продемонстрируем на примере Filter:

struct Filter: Encodable {
        let director: String
    }

struct Variable: Encodable {
        let filter: Filter
    }

    let filter = Filter(director: "Irvin Kershner")
    let variable = Variable(filter: filter)

У Filter, в свою очередь, тоже существует несколько опциональных свойств. Мы берем director только для упрощения демонстрационного кода.

Структуры Filter и Variable принимают протокол Encodable для подготовки тела в формате JSON.

4.Создание HTTP-запроса

struct Payload<T: Encodable>: Encodable {
        let variables: T
        let query: String
    }

    let payload = Payload(variables: variable, query: allFilmsQuery)
    var request = URLRequest(url: swapiURL)
    request.httpBody = try! JSONEncoder().encode(payload)
    request.httpMethod = "POST"
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accept")
    URLSession.shared.dataTask(with: request) { (data, response, error) in
        guard let data = data else {
            return print("data is null")
        }
        let swiftyJsonVar = try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
        print(swiftyJsonVar)
    }.resume()
struct Payload<T: Encodable>: Encodable {
        let variables: T
        let query: String
    }

    let payload = Payload(variables: variable, query: allFilmsQuery)
    var request = URLRequest(url: swapiURL)
    request.httpBody = try! JSONEncoder().encode(payload)
    request.httpMethod = "POST"
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accept")
    URLSession.shared.dataTask(with: request) { (data, response, error) in
        guard let data = data else {
            return print("data is null")
        }
        let swiftyJsonVar = try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
        print(swiftyJsonVar)
    }.resume()

Давайте разбираться:

  • Структура Payload содержит variables и query. Она также перенимает протокол Encodable.
  • request представляет собой экземпляр URLRequest, встроенный в Star War API URL: https://swapi.graph.cool.
  • payload кодируется в формате JSON и добавляется к телу запроса request.
  • Метод POST задействован согласно требованию Star War GraphQL API.
  • Для значений Content-Type and Accept в поле заголовка HTTP используется application/json.
  • request просто запускается одновременно с общим экземпляром URLSession.
  • Ответные данные находятся в формате JSON и могут быть десериализованы посредством JSONSerialization.

Заключение

В этой статье описывается наиболее облегчённый способ взаимодействия со службой GraphQL. Необработанный HTTP-запрос устраняет необходимость каких бы то ни было сторонних библиотек. Быстрый и чистый код целиком находится под контролем разработчика. 

Ресурсы

Все сторонние библиотеки, API GraphQL и образцы кода Swift можно найти по следующим ссылкам:

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи: Eric Yang, “The Easiest Way to Perform GraphQL Queries in iOS”