Статьи

Работа с сетью в Swift — часть 3

Серия статей о работе с сетью состоит из трёх частей:

Для примера будем рассматривать работу с вот этим сервисом, который позволяет делать запросы без авторизации.

Первая конструкция, которую мы напишем для структурирования запросов к API — это enum со всеми возможными запросами:
enum ApiType {
  case login
  case getUsers
  case getPosts
  case getAlbums
}
Здесь мы видим 4 вида запросов:

  • Авторизация пользователя (придумали сами для примера);
  • Получение списка пользователей;
  • Получение списка постов;
  • Получение списка альбомов.

Также в этот enum необходимо добавить 4 обязательные переменные:

  • baseURL — общий URL для всех запросов;
  • headers — хедеры запроса;
  • path — путь запроса;
  • request — сам запрос.

baseURL

Этот URL является общим для всех запросов. Вставим этот код в наш enum:
var baseURL: URL {
  return URL(string: “https://jsonplaceholder.typicode.com/")!
}
headers

Хедеры, как и все последующие переменные, меняются в зависимости от типа запроса: авторизационные хедеры должны содержать токен авторизации, а остальные запросы могут содержать пустые хедеры. Говоря проще, для запроса login хедеры должны быть вида [“auth-token”: “12345”], а для всех остальных запросов — вида [:]. Запишем это в коде:
var headers: [String: String] {
  switch self {
  case .login: return [“auth-token”: “12345”]
  default: return [:]
  }
}
path

Эта переменная должна содержать путь до запроса за исключением той его части, которая содержится в baseURL:
var path: String {
  switch self {
  case .login: return “login”
  case .getUsers: return “users”
  case .getPosts: return “posts”
  case .getAlbums: return “albums”
  }
}
request

Переменную request нужно построить исходя из нашего запроса. Для начала нужно построить URL, состоящий из baseURL и path. Далее нужно создать сам запрос и присвоить ему хедеры из переменной headers. После этого можно провести индивидуальную настройку для каждого запроса. В нашем случае мы лишь определяем метод запроса:
var request: URLRequest {
  let url = URL(string: path, relativeTo: baseURL)!
  var request = URLRequest(url: url)
  request.allHTTPHeaderFields = headers
  switch self {
    case .login:
    request.httpMethod = “GET”
    return request
    default:
    request.httpMethod = “GET”
    return request
  }
}
Вообще говоря, делать разбивку между .login и остальными запросами в данном случае не обязательно, но мы поступили так для примера и наглядности.

Итоговый ApiType должны выглядеть так:
Отлично, теперь приступим к настройке моделей, по которым мы будем парсить приходящие данные. На сайте с документациейперейдем по ссылке, чтобы увидеть формат приходящих данных. Скопируем текст целиком и отправим его в Quicktype. Убедимся, что мы сделали все поля опциональными (это нужно, т.к. мы не уверены, что сервис всегда будет отправлять нам все поля. А вдруг у какого-то пользователя не указан возраст):
Quicktype сгенерировал для нас несколько структур:
Скопируем код структур и перенесем её в новый файл User.swift. После этого у нас должна быть примерно такая структура файлов:
Проделаем аналогичное действие для остальных запросов. В результате мы получим следующую картину:
Отлично, теперь можно приступать к методам для реализации запросов. Доступ к веб-сервису может понадобиться в разных частях приложения. Для этого удобно создать единую точку доступа к сервису, к которой будут обращаться разные модули внутри вашего приложения. Самое простое решение сводится к использованию синглтона. Создадим класс ApiManager и реализуем его единственный экземпляр, который назовем shared:
class ApiManager {
static let shared = ApiManager()
}
Напишем в нём функцию для получения списка пользователей:
Разберемся, что происходит в каждой строчке:

  • 59: Объявляем функцию с completion-блоком и атрибутом @escaping. Этот атрибут говорит о том, что нам нужно дождаться ответа от сервера.
  • 60: Создаем запрос с помощью ApiType, который мы спроектировали ранее.
  • 61: Создаем dataTask с запросом, созданным на предыдущем шаге. Получаем от сервера данные (data), ответ (response) и код ошибки (error).
  • 62: Проверяем, что data не равна nil, а затем проверяем, что data можно распарсить по структуре Users.
  • 63. Если все хорошо, то возвращаем список пользователей.
  • 65: Если data=nil, или данные не получилось распарсить, что возвращаем пустой массив пользователей.
  • 68: Запускаем dataTask по загрузке данных.

Проверить правильность работы кода можно в любом месте приложения, просто вызвав:
ApiManager.shared.getUsers { users in
  print(users)
}
Итоговый класс ApiManager со всеми необходимыми методами должен выглядеть вот так:
Примерно похожую, но немного усложненную структуру файлов и классов я использую в своих проектах. Вы можете организовать работу с API так, либо любым другим удобным способом.

Навыки работы с API можно отработать на вот этих сервисах, а если что-то не получается, то проблемой можно поделиться в нашем чате.

Всем пока!
Сеть