Следующая статья поможет вам: Сеть в Swift с URLSession404 — не найдена
Вернуться в блог
В этом руководстве по разработке приложений мы обсудим, как можно использовать набор компонентов, классов и функций URLSession для выполнения сетевых запросов HTTP GET и POST. Вы узнаете, как проверять данные ответов и как добавлять в запросы дополнительные параметры, например заголовки.
Почти каждое приложение в какой-то момент будет взаимодействовать с Интернетом. Как это работает? Какой код Swift вы можете использовать для выполнения сетевых запросов HTTP(S)?
Получение и загрузка данных из веб-сервисов и в них — это навык, которым должен овладеть любой прагматичный разработчик iOS, а URLSession предлагает лучший в своем классе API для выполнения сетевых запросов.
Представьте, что вы делаете Twitter приложение. В какой-то момент приложению необходимо запросить данные у TwitterAPI. Когда пользователь просматривает свою временную шкалу, вы можете использовать Twitter API для получения информации об их твитах.
Twitter API — это веб-сервис, который отвечает на запросы HTTP(S). В iOS мы можем использовать систему загрузки URL-адресов для настройки и выполнения HTTP-запросов. Это называется сетью, и это основа любого современного приложения для iOS — почти все приложения в какой-то момент взаимодействуют с серверами в Интернете.
Быстрый Note: С точки зрения безопасности важно выработать привычку использовать по умолчанию HTTPS, то есть сетевые запросы, зашифрованные с помощью SSL/TLS, при работе с URL-адресами. Вы можете получить SSL-сертификаты бесплатно через Let’s Encrypt.
Начиная с iOS 7, фактическим способом выполнения сетевых HTTP-запросов является использование класса URLSession. Класс URLSession на самом деле является частью группы классов, которые работают вместе, создавая HTTP-запросы и отвечая на них.
Многие разработчики также полагаются на сторонние библиотеки, такие как Alamofire, но вскоре вы обнаружите, что вам не обязательно зависеть от библиотеки для простой работы в сети HTTP. В классе URLSession уже есть все, что нам нужно.
Вот как работает среда:
- Вы используете URLSession для создания сеанса. Вы можете представить себе сеанс как открытую вкладку или окно в вашем веб-браузере, которое группирует множество HTTP-запросов при последующих посещениях веб-сайта.
- URLSession используется для создания экземпляров URLSessionTask, которые могут извлекать и возвращать данные в ваше приложение, а также загружать и отправлять файлы в веб-сервисы.
- Вы настраиваете сеанс с помощью объекта URLSessionConfiguration. Эта конфигурация управляет кэшированием, файлами cookie, подключением и учетными данными.
- Чтобы сделать запрос, вы создаете задачу обработки данных класса URLSessionDataTask и предоставляете ей URL-адрес, например https://twitter.com/api/, и обработчик завершения. Это замыкание, которое выполняется, когда ответ на запрос возвращается в ваше приложение.
- Когда обработчик завершения выполняется, вы можете проверить возвращенные данные и предпринять соответствующие действия, например загрузить данные в пользовательский интерфейс Tweet.
Вы используете URLSession для выполнения нескольких последующих запросов в качестве экземпляров URLSessionTask. Задача всегда является частью сеанса. URLSession также действует как фабрика, которую вы используете для настройки и выполнения различных задач URLSessionTask на основе введенных вами параметров.
В URLSession мы различаем три типа задач:
- Задачи данных отправляют и получают данные с помощью URLSessionDataTask, используя объекты NSData. Они наиболее распространены для запросов веб-сервисов, например, при работе с JSON.
- Задачи загрузки отправляют файлы на веб-сервер с помощью URLSessionUploadTask. Они похожи на задачи обработки данных, но экземпляры URLSessionUploadTask также могут загружать данные в фоновом режиме (или когда приложение приостановлено).
- Задачи загрузки загружают файлы с веб-сервера с помощью URLSessionDownloadTask путем прямой записи во временный файл. Вы можете отслеживать ход загрузки файлов, а также приостанавливать и возобновлять ее.
Давайте начнем с выполнения нескольких сетевых запросов с помощью URLSession!
Официальный Apple Документация по URLSession обширна, но она не так организована, как хотелось бы. Хорошей отправной точкой является система загрузки URL-адресов и последующее чтение связанных статей, таких как «Извлечение данных веб-сайта в память». И если вам нужен учебник о том, как максимально эффективно использовать AppleДокументацию разработчика, обязательно прочтите «Как использовать» AppleДокументация разработчика для развлечения и прибыли.
Давайте получим некоторые данные с помощью URLSession! Вот что мы собираемся сделать:
- Настройте HTTP-запрос с помощью URLSession.
- Сделайте запрос с помощью URLSessionDataTask.
- Быстрая печать возвращенных данных ответа
- Правильно проверяйте данные ответа
- Преобразуйте данные ответа в JSON
Настройте HTTP-запрос с помощью URLSession.
Сначала нам нужно настроить запрос, который мы хотим сделать. Как обсуждалось ранее, нам понадобится URL-адрес и сеанс. Так:
пусть сеанс = URLSession.shared
let url = URL(строка: «…»)!
URL-адрес, который мы запросим, — user.json (щелкните правой кнопкой мыши, затем «Копировать ссылку»).
В приведенном выше коде мы инициализируем константу URL-адреса типа URL. Используемый нами инициализатор не работает, но поскольку мы уверены, что URL-адрес правильный, мы используем принудительное развертывание для обработки необязательного параметра.
URLSession.shared Singleton — это ссылка на общий экземпляр URLSession, который не имеет конфигурации. Это более ограничено, чем сеанс, инициализируемый с помощью объекта URLSessionConfiguration, но на данный момент это нормально.
Сделайте запрос с помощью URLSessionDataTask.
Далее мы собираемся создать задачу обработки данных с помощью функции dataTask(with:completionHandler:) класса URLSession. Так:
let Task = session.dataTask(with: URL, CompleteHandler: {данные, ответ, ошибка в
// Сделай что-нибудь…
})
Здесь происходит несколько вещей. Во-первых, обратите внимание, что мы присваиваем возвращаемое значение dataTask(with:completionHandler:) константе задачи. Это та задача данных, которая, как обсуждалось ранее, имеет тип URLSessionDataTask.
dataTask(with:completionHandler:) имеет два параметра: URL-адрес запроса и обработчик завершения. Мы создали этот URL-адрес запроса ранее, так что это несложно.
Обработчик завершения немного сложнее. Это закрытие, которое выполняется после завершения запроса, то есть при получении ответа от веб-сервера. Это может быть любой тип ответа, включая ошибки, тайм-ауты, ошибки 404 и фактические данные JSON.
Замыкание имеет три параметра: объект данных ответа, объект URLResponse и объект Error. Все эти параметры замыкания являются необязательными, поэтому они могут быть равны нулю.
Каждый из этих параметров имеет определенную цель:
- Мы можем использовать объект данных типа Data, чтобы проверить, какие данные мы получили обратно от веб-сервера, например запрошенный нами JSON.
- Объект ответа типа URLResponse может рассказать нам больше об ответе на запрос, например, о его длине, кодировке, коде состояния HTTP, полях возвращаемого заголовка и т. д.
- Объект error содержит объект Error, если при выполнении запроса произошла ошибка. Когда ошибок не произошло, это просто ноль.
Природа HTTP-запросов, мягко говоря, нестабильна. Вам нужно будет проверить все, что вы получите: ошибки, ожидаемые коды состояния HTTP, неверный формат JSON и т. д. Вы можете получить ответ «200 ОК» с помощью HTML, даже если вы ожидаете JSON. Позже вы увидите, как мы с этим справимся.
На данный момент сетевой запрос еще не выполнен! Его только установили. Вот как вы начинаете запрос:
задача.резюме()
При вызове функции возобновления() для объекта задачи запрос выполняется и в какой-то момент вызывается обработчик завершения. Легко забыть о вызове summary(), поэтому убедитесь, что вы этого не делаете!
Вы можете использовать делегирование с URLSessionDelegate вместо обработчиков завершения. Лично я считаю использование замыканий более удобным, особенно потому, что вы можете использовать промисы и PromiseKit, чтобы с ними легче справиться.
Распечатайте возвращенные данные ответа
Просто ради интереса давайте проверим, что на самом деле мы получаем в обработчике завершения. Вот соответствующий код:
let Task = session.dataTask(with: url) {данные, ответ, ошибка в
распечатать (данные)
распечатать (ответ)
печать (ошибка)
}
Быстрый Note: В приведенном выше фрагменте используется синтаксис замыкающего закрытия. Когда последний параметр функции принимает замыкание, вы можете написать это замыкание вне круглых скобок функции (). Читать становится намного легче!
Когда приведенный выше код выполняется, это распечатывается:
- Значение данных печатает что-то вроде Необязательно (321 байт). Хм, почему это? Это потому, что данные — это объект данных, поэтому у них пока нет визуального представления. Мы можем преобразовать или интерпретировать его как JSON, но для этого потребуется дополнительный код.
- Ответ имеет тип NSHTTPURLResponse, подкласс URLResponse, и содержит массу данных о самом ответе. Код статуса HTTP — 200, и из заголовков HTTP мы видим, что этот запрос прошел через Cloudflare.
- А ошибка? Это ноль. К счастью, обработчику завершения не было передано никаких ошибок. Однако это не означает, что запрос одобрен!
Правильно проверяйте данные ответа
Хорошо, давайте проведем проверку в обработчике завершения. Когда вы отправляете сетевые запросы HTTP, вам необходимо проверить как минимум следующее:
- Возникли ли какие-либо ошибки? Вы можете проверить это с помощью переданного объекта ошибки.
- Ожидается ли код ответа HTTP?
- Вы получили данные в правильном формате?
Сначала давайте проверим, равна ли ошибка нулю или нет. Вот как:
если ошибка != ноль {
// О, НЕТ! Произошла ошибка…
self.handleClientError (ошибка)
возвращаться
}
Что делать внутри условного выражения error != nil? Две рекомендации:
- Вызовите функцию, которая может обработать ответ, и предпримите соответствующие действия, как в примере выше.
- Вызовите ошибку с помощью throw и используйте промисы для устранения любых выброшенных или переданных ошибок в предложении .catch цепочки.
Затем давайте проверим, в порядке ли код состояния HTTP. Вот как:
охранник пусть httpResponse = ответ как? HTTPURLОтвет,
(200…299).contains(httpResponse.statusCode) else {
self.handleServerError(ответ)
возвращаться
}
Вот что происходит:
- Синтаксис Guard let проверяет, являются ли два условия ложными, и когда это происходит, выполняется предложение else. Он буквально «охраняет» истинность этих двух условий. Проще всего это прочитать так: «Следите за тем, чтобы ответом был HTTPURLResponse, а statusCode находился в диапазоне от 200 до 299, в противном случае вызовите handleServerError()».
- Первое условие — это необязательное преобразование ответа типа URLResponse к HTTPURLResponse. Это приведение гарантирует, что мы сможем использовать свойство statusCode в ответе, которое является лишь частью HTTPURLResponse.
- Диапазон (200…299) представляет собой последовательность кодов состояния HTTP, которые считаются ОК. Вы можете проверить все коды состояния HTTP здесь. Итак, когда statusCode содержится в диапазоне 200…299, ответ в порядке.
- Когда это не так, например, если мы получаем внутреннюю ошибку сервера 500, вызывается функция handleServerError(), и мы возвращаем замыкание.
Следующая проверка, которую мы собираемся выполнить, проверяет так называемый MIME-тип ответа. Это значение, возвращаемое большинством веб-серверов, которое объясняет формат данных ответа. Мы ожидаем JSON, поэтому проверим вот что:
Guard let mime = response.mimeType, mime == «application/json» else {
print(“Неправильный тип MIME!”)
возвращаться
}
В приведенном выше коде используется тот же синтаксис Guard Let, чтобы гарантировать, что response.mimeType равен application/json. Если этого не произойдет, нам придется отреагировать соответствующим образом и попытаться исправить ошибку.
Вы видите, что может возникнуть большое количество ошибок, и вам придется проверить большинство из них, если не все. И мы даже не разобрались с ошибками приложений, такими как «Неверный пароль!» или «Неизвестный идентификатор пользователя!» Разумно подумать, с какими ошибками вы столкнетесь, и разработать стратегию или модель для последовательного и надежного их устранения.
Преобразуйте данные ответа в JSON
Теперь, когда мы уверены, что ответ в порядке, мы можем проанализировать его и получить объект JSON. Вот как:
если позволить json = попробовать? JSONSerialization.jsonObject(с: данными!, параметры: []) {
печать (JSON)
}
И вот что происходит в приведенном выше коде:
- Мы используем функцию jsonObject(with:options:) класса JSONSerialization для сериализации данных в JSON. По сути, данные считываются посимвольно и превращаются в объект JSON, который нам легче читать и манипулировать им. Это похоже на то, как вы читаете книгу слово за словом, а затем превращаете это в историю в своей голове.
- Необязательная привязка с try? — это трюк, который вы можете временно использовать, чтобы отключить любые ошибки от jsonObject(…). Короче говоря, во время сериализации могут возникать ошибки, и когда они происходят, возвращаемое значение jsonObject(…) равно нулю, и условие не продолжает выполнение.
Здесь стоит отметить, что правильный способ борьбы с ошибками заключается в следующем:
делать {
let json = попробуйте JSONSerialization.jsonObject(with: data!, параметры: [])
печать (JSON)
} ловить {
print(“Ошибка JSON: \(error.localizedDescription)”)
}
Вам не обязательно использовать JSONSerialization; отличная альтернатива — использование Codable для декодирования данных JSON. Аккуратный!
В приведенном выше коде ошибки, возникающие в строке, отмеченной знаком try, перехватываются в блоке catch. Мы также могли бы повторно выдать ошибку и обработать ее в другой части кода.
Если данные JSON в порядке, они присваиваются константе json и распечатываются. И мы, наконец, видим, что это данные ответа с того URL-адреса, с которого мы начали:
(
{
возраст = 5000;
«первое_имя» = Форд;
«фамилия» = префект;
},
{
возраст = 999;
«first_name» = Зафод;
«last_name» = Библброкс;
},
{
возраст = 42;
«first_name» = Артур;
«фамилия» = Дент;
},
{
возраст = 1234;
«first_name» = Триллиан;
«фамилия» = Астра;
}
)
Потрясающий! Вот полный код, который мы написали на данный момент:
пусть сеанс = URLSession.shared
let url = URL(строка: «…»)!
let Task = session.dataTask(with: url) {данные, ответ, ошибка в
если ошибка != ноль || данные == ноль {
print(“Ошибка клиента!”)
возвращаться
}
охранник пусть ответ = ответ как? HTTPURLResponse, (200…299).contains(response.statusCode) else {
print(“Ошибка сервера!”)
возвращаться
}
Guard let mime = response.mimeType, mime == «application/json» else {
print(“Неправильный тип MIME!”)
возвращаться
}
делать {
let json = попробуйте JSONSerialization.jsonObject(with: data!, параметры: [])
печать (JSON)
} ловить {
print(“Ошибка JSON: \(error.localizedDescription)”)
}
}
задача.резюме()
Быстрая подсказка: Если вы запустите приведенный выше код в Xcode Playground, разумно использовать PlaygroundPage.current.needsIndefiniteExecution = true, чтобы включить бесконечное выполнение. Вы можете снова остановить игровую площадку с помощью PlaygroundPage.current.finishExecution(), например, когда возвращается асинхронный HTTP-запрос. Не забудьте импортировать PlaygroundSupport.
Другая типичная задача сети HTTP — загрузка данных на веб-сервер и, в частности, выполнение так называемых запросов POST. Вместо получения данных с веб-сервера мы теперь будем отправлять данные обратно на этот веб-сервер.
Хорошим примером является вход на веб-сайт. Ваше имя пользователя и пароль отправляются на веб-сервер. Затем этот веб-сервер сверяет ваше имя пользователя и пароль с тем, что хранится в базе данных, и отправляет ответ обратно. Аналогично, когда ваш Twitter приложение используется для создания нового твита, вы отправляете POST-запрос на Twitter API с текстом твита.
Вот что мы сделаем:
- Настройте запрос HTTP POST с помощью URLSession.
- Настройте заголовки и тело запроса
- Сделайте запрос с помощью URLSessionUploadTask.
- Распечатайте возвращенные данные ответа
Настройте запрос HTTP POST с помощью URLSession.
Выполнение POST-запросов с помощью URLSession в основном сводится к настройке запроса. Мы сделаем это, добавив некоторые данные в объект URLRequest. Вот как:
пусть сеанс = URLSession.shared
let url = URL(строка: «https://example.com/post»)!
запрос вар = URLRequest (url: URL)
request.httpMethod = «POST»
С помощью приведенного выше кода мы сначала создаем константу сеанса с общим экземпляром URLSession и настраиваем объект URL, который ссылается на https://example.com/post.
Затем с помощью этого объекта URL мы создаем экземпляр URLRequest и присваиваем его переменной запроса. В последней строке мы меняем httpMethod на POST.
Настройте заголовки и тело запроса
Вы также можете использовать объект URLRequest для установки заголовков HTTP. Заголовок — это специальный параметр, который отправляется как часть запроса и обычно содержит специальную информацию для веб-сервера или веб-приложения. Хорошим примером является заголовок Cookie, который используется для отправки информации о файлах cookie туда и обратно.
Добавим в запрос несколько заголовков:
request.setValue(“application/json”, forHTTPHeaderField: “Content-Type”)
request.setValue(“Powered by Swift!”, forHTTPHeaderField: “X-Powered-By”)
Это довольно легко. Вы можете установить значение данного поля заголовка. Первый заголовок указывает, что тип запроса — JSON, а второй заголовок просто поддельный.
Запросу нужно тело. Это некоторые данные, обычно текстовые, которые отправляются как часть сообщения запроса. В нашем случае это будет объект JSON, отправленный как объект данных.
Начнем с создания простого словаря с некоторыми значениями:
пусть json = [
“username”: “zaphod42”,
“message”: “So long, thanks for all the fish!”
]
Затем мы превращаем этот словарь в объект Data с помощью:
пусть jsonData = попробуй! JSONSerialization.data(withJSONObject: json, параметры: [])
В приведенном выше шаге используется тот же класс JSONSerialization, который мы использовали ранее, но на этот раз он делает прямо противоположное: превращает объект в объект Data, использующий формат JSON.
Он также использует try! чтобы отключить обработку ошибок, но имейте в виду, что в рабочем приложении вам необходимо будет соответствующим образом обрабатывать ошибки.
Для этого вам не обязательно использовать JSONSerialization; отличная альтернатива — Codable!
Сделайте запрос с помощью URLSessionUploadTask.
Теперь мы можем отправить jsonData на веб-сервер с помощью экземпляра URLSessionUploadTask. Это похоже на то, что мы делали раньше, за исключением того, что для создания задачи мы будем использовать запрос и данные, а не только URL-адрес.
Вот как:
let Task = session.uploadTask(with: request, from: jsonData) {данные, ответ, ошибка в
// Сделай что-нибудь…
}
задача.резюме()
В приведенном выше коде мы создаем задачу загрузки с помощью session.uploadTask(…) и указываем request и jsonData в качестве параметров. Вместо создания простой задачи по обработке данных приведенный выше запрос будет включать те заголовки, тело и URL-адрес, которые мы настроили. Как и раньше , мы можем указать обработчик завершения, и запрос запускается после вызова Task.resume().
Распечатайте возвращенные данные ответа
Внутри обработчика завершения мы должны проверить ответ и предпринять соответствующие действия. На данный момент можно просто прочитать данные ответа с помощью:
если пусть данные = данные, пусть dataString = String (данные: данные, кодировка: .utf8) {
печать (строка данных)
}
В приведенном выше коде используется необязательная привязка для преобразования необязательных данных в экземпляр String. А поскольку URL-адрес https://example.com/post не отвечает на запросы POST, мы получаем красивое сообщение об ошибке в формате HTML:
404 Не Найдено
И с помощью следующего кода мы видим, что код состояния HTTP на самом деле 404 Not Found. Так:
если let httpResponse = ответ как? HTTPURLResponse {
печать (httpResponse.statusCode)
}
Потрясающий!
Быстрая подсказка: Если вы хотите отлаживать сетевые запросы, я рекомендую Charles Proxy. А если вы хотите проверить или имитировать запросы и API веб-сервисов, воспользуйтесь отличным приложением Paw.
Этот маленький танец, который вы совершаете при отправке HTTP-запроса, присущ тому, как работает Интернет. Вы запрашиваете ресурс с веб-сервера, проверяете ответ и предпринимаете соответствующие действия.
В iOS вы можете использовать URLSession для настройки и выполнения сетевых запросов. Это настолько просто, насколько это возможно, с практическими объектами, такими как HTTPURLResponse, которые дают представление о том, что происходит.