Avatar ·

Как правильно кодировать URL в Swift? Проблема с кириллицей и спецсимволами

Всем привет! Я новичок в Swift и столкнулся с проблемой при работе с URL. Мне нужно отправить GET-запрос на сервер с параметрами, которые содержат кириллические символы и пробелы. Например, я формирую строку запроса так: ```swift let searchText = "Москва центр" let urlString = "https://api.example.com/search?q=\(searchText)" let url = URL(string: urlString) ``` Но URL получается nil, потому что строка содержит недопустимые символы. Я пробовал использовать `addingPercentEncoding(withAllowedCharacters:)`, но не уверен, какой набор символов использовать. Пробовал `urlQueryAllowed` — кириллица кодируется, но пробелы остаются. Если использую `urlHostAllowed`, то всё ломается. Подскажите, как правильно и безопасно кодировать URL в Swift? Нужно ли кодировать каждый параметр отдельно или всю строку целиком? И как быть, если в значении параметра уже есть закодированные последовательности (например, %20)? Заранее спасибо! <<>>

Правильный способ кодирования URL в Swift

Проблема, с которой вы столкнулись, очень распространена. Главное правило: кодировать нужно каждый компонент URL отдельно, а не всю строку целиком. Это связано с тем, что разные части URL имеют разные допустимые наборы символов.

Основное решение

Используйте addingPercentEncoding(withAllowedCharacters:) с правильным набором символов для каждой части URL. Для query-параметров (часть после ?) лучше всего использовать .urlQueryAllowed, но с одним важным дополнением — он не кодирует символ & и =, которые могут сломать URL, если они есть в значении параметра.

// Правильный способ кодирования параметров запроса
let searchText = "Москва центр & Подмосковье"
let encodedQuery = searchText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
// Результат: "Москва%20центр%20&%20Подмосковье"
// Проблема: & не закодирован!

Поэтому для полной безопасности лучше создать свой набор символов:

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

// Использование
let searchText = "Москва центр & Подмосковье"
if let encoded = searchText.urlEncoded() {
    let urlString = "https://api.example.com/search?q=\(encoded)"
    print(urlString) // https://api.example.com/search?q=%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%20%D1%86%D0%B5%D0%BD%D1%82%D1%80%20%26%20%D0%9F%D0%BE%D0%B4%D0%BC%D0%BE%D1%81%D0%BA%D0%BE%D0%B2%D1%8C%D0%B5
}

Работа с несколькими параметрами

Для удобства работы с несколькими параметрами рекомендую использовать URLComponents:

func buildURL(base: String, params: [String: String]) -> URL? {
    guard var components = URLComponents(string: base) else { return nil }
    
    components.queryItems = params.map { (key, value) in
        URLQueryItem(name: key, value: value)
    }
    
    return components.url
}

// Пример использования
let params = [
    "q": "Москва центр",
    "category": "недвижимость",
    "page": "1"
]

if let url = buildURL(base: "https://api.example.com/search", params: params) {
    print(url.absoluteString)
    // https://api.example.com/search?q=%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%20%D1%86%D0%B5%D0%BD%D1%82%D1%80&category=%D0%BD%D0%B5%D0%B4%D0%B2%D0%B8%D0%B6%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C&page=1
}

URLComponents автоматически правильно кодирует каждый параметр, учитывая все тонкости. Это самый надёжный и рекомендуемый способ.

Важные моменты

  • Не кодируйте уже закодированные строки. Если вы получили данные, которые уже содержат %20, повторное кодирование превратит %20 в %2520, что сломает запрос.
  • Кодируйте только значения, не ключи. Ключи параметров обычно не содержат спецсимволов, но если сомневаетесь — кодируйте и их.
  • Для path-компонентов (части URL до ?) используйте .urlPathAllowed.
  • Для host-компонентов используйте .urlHostAllowed.

Полный пример с обработкой ошибок

func search(query: String, completion: @escaping (Result) -> Void) {
    let params = ["q": query]
    
    guard var components = URLComponents(string: "https://api.example.com/search") else {
        completion(.failure(URLError(.badURL)))
        return
    }
    
    components.queryItems = params.map { URLQueryItem(name: $0.key, value: $0.value) }
    
    guard let url = components.url else {
        completion(.failure(URLError(.badURL)))
        return
    }
    
    URLSession.shared.dataTask(with: url) { data, response, error in
        if let error = error {
            completion(.failure(error))
        } else if let data = data {
            completion(.success(data))
        }
    }.resume()
}

Используйте URLComponents — это стандартный и безопасный способ работы с URL в Swift. Он избавит вас от множества проблем с кодированием.

<<>>

Альтернативный подход: использование Alamofire

Если вы используете популярную библиотеку Alamofire для сетевых запросов, то вся работа с кодированием URL берётся на себя библиотекой. Вам не нужно вручную кодировать параметры:

import Alamofire

let parameters: [String: String] = [
    "q": "Москва центр",
    "category": "недвижимость"
]

AF.request("https://api.example.com/search", 
           parameters: parameters)
  .responseJSON { response in
      // Обработка ответа
  }

По умолчанию Alamofire использует URLEncoding.default, который правильно кодирует все параметры. Если вам нужно изменить поведение (например, использовать query-строку вместо тела запроса для POST), можно явно указать:

AF.request(url, 
           parameters: parameters, 
           encoding: URLEncoding.queryString)

Этот подход особенно удобен для больших проектов, где много сетевых запросов. Однако для простых проектов или изучения Swift рекомендую сначала освоить работу с URLComponents из первого ответа — это даст лучшее понимание того, как всё работает "под капотом".