Avatar ·

Как правильно закодировать URL (URL encode) в Swift для iOS?

Всем привет! Я новичок в Swift и iOS разработке, и у меня возникла проблема с кодированием URL. Мне нужно отправить GET-запрос к API, который принимает параметры в строке запроса. Один из параметров — это строка с кириллицей и спецсимволами (например, "Привет, мир! & тест"). Я пробовал использовать `addingPercentEncoding(withAllowedCharacters:)`, но у меня ничего не получается. Вот код, который я написал: ```swift let baseURL = "https://api.example.com/search" let query = "Привет, мир! & тест" let encodedQuery = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) let fullURL = "\(baseURL)?q=\(encodedQuery)" print(fullURL) ``` Но в консоли я вижу, что пробелы превращаются в `%20`, а символ `&` остается как есть. Из-за этого сервер неправильно парсит запрос, и я получаю ошибку. В документации написано, что `urlQueryAllowed` разрешает символ `&`, но ведь он разделяет параметры! Как правильно закодировать весь URL, чтобы все спецсимволы были экранированы? Также я слышал про `URLComponents`, но не совсем понимаю, как им пользоваться. Может, есть более простой способ? Буду благодарен за подробный пример с объяснением. <<>>

Проблема: почему простое кодирование не работает

Вы столкнулись с классической проблемой: .urlQueryAllowed действительно разрешает символ &, так как технически он допустим в значении параметра. Но когда вы вставляете закодированную строку напрямую в URL, символ & интерпретируется как разделитель параметров, а не как часть значения. Это ломает запрос.

Правильное решение: используем URLComponents

Самый надежный способ кодирования URL в Swift — это использование URLComponents и URLQueryItem. Эти классы автоматически экранируют все необходимые символы, включая &, =, #, + и пробелы. Вот как это работает:

import Foundation

let baseURL = "https://api.example.com/search"
let query = "Привет, мир! & тест"

// 1. Создаем URLComponents из базового URL
guard var components = URLComponents(string: baseURL) else {
    fatalError("Неверный базовый URL")
}

// 2. Создаем массив query items (параметров)
let queryItem = URLQueryItem(name: "q", value: query)
components.queryItems = [queryItem]

// 3. Получаем итоговый URL
guard let finalURL = components.url else {
    fatalError("Не удалось создать URL")
}

print(finalURL.absoluteString)
// Вывод: https://api.example.com/search?q=%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%2C%20%D0%BC%D0%B8%D1%80!%20%26%20%D1%82%D0%B5%D1%81%D1%82

Обратите внимание: символ & теперь корректно закодирован как %26, а пробелы — как %20. Кириллица тоже правильно преобразована в процентную кодировку (UTF-8).

Почему это работает?

  • URLComponents знает, какие символы должны быть закодированы в разных частях URL (схема, хост, путь, query).
  • URLQueryItem автоматически кодирует и имя, и значение параметра, экранируя &, = и другие опасные символы.
  • Итоговый URL гарантированно валиден и может быть передан в URLSession или открыт в браузере.

Что делать с несколькими параметрами?

Просто добавьте несколько URLQueryItem в массив:

let params = [
    URLQueryItem(name: "q", value: "Привет, мир! & тест"),
    URLQueryItem(name: "page", value: "1"),
    URLQueryItem(name: "limit", value: "10")
]
components.queryItems = params

Когда можно использовать addingPercentEncoding?

Метод addingPercentEncoding(withAllowedCharacters:) полезен, когда нужно закодировать только часть строки, которая не является полным значением параметра. Например, для кодирования отдельного компонента пути:

let pathComponent = "файл с пробелом.txt"
let encodedPath = pathComponent.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)
let url = URL(string: "https://example.com/\(encodedPath)")

Но для query-параметров всегда используйте URLComponents — это избавит вас от множества головных болей.

Дополнительный совет: декодирование

Если вам нужно декодировать закодированный URL обратно в читаемую строку, используйте removingPercentEncoding:

let encoded = "%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%2C%20%D0%BC%D0%B8%D1%80!"
let decoded = encoded.removingPercentEncoding
print(decoded ?? "") // "Привет, мир!"

В итоге: забудьте про ручное кодирование query-параметров через addingPercentEncoding. URLComponents + URLQueryItem — это стандартный и правильный путь в современном Swift.

<<>>

Альтернативный подход: форматирование строки с CharacterSet

Если вы по каким-то причинам не можете использовать URLComponents (например, работаете с очень старым кодом или вам нужно закодировать только одно значение), можно расширить CharacterSet для экранирования всех опасных символов:

extension String {
    func urlEncoded() -> String? {
        let allowed = CharacterSet.urlQueryAllowed
            .subtracting(CharacterSet(charactersIn: "&+=#%@"))
        return self.addingPercentEncoding(withAllowedCharacters: allowed)
    }
}

let query = "Привет, мир! & тест"
if let encoded = query.urlEncoded() {
    let url = "https://api.example.com/search?q=\(encoded)"
    print(url)
    // https://api.example.com/search?q=%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%2C%20%D0%BC%D0%B8%D1%80!%20%26%20%D1%82%D0%B5%D1%81%D1%82
}

Этот метод вычитает из стандартного набора .urlQueryAllowed символы, которые могут сломать URL (&, =, #, %, @).

Когда это может пригодиться?

  • Если вы работаете с URLSession напрямую и не хотите создавать объекты URLComponents.
  • Для простых случаев с одним параметром.
  • Если вам нужно закодировать только значение, а имя параметра уже безопасно.

Однако помните: URLComponents — это более универсальное и поддерживаемое решение. Мой вариант — для случаев, когда нужно быстрое и простое кодирование без лишних объектов.