URL에 한글이 들어갈때

URL(string:)

네트워크 호출시 서버에서 URL타입을 내려줄 때 리소스 등에 종종 한글이 포함된 채로 내려주는 경우가 있다.
URL은 아스키코드 기반이기 때문에 그대로 URL(string:)이 nil이 되어 버린다. 그러므로 적절하게 인코딩해주어야 하는데, String의 메소드인 addingPercentEncoding(withAllowedCharacters:)에 .urlQueryAllowed를 넣어서 인코딩하면 된다.


let urlString = "https://네이버.com"
let url: URL = URL(string: urlString) // nil

if let encodedURL = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
    let url: URL = URL(string: urlString) // https://%EB%84%A4%EC%9D%B4%EB%B2%84.com
}

String에서 URL을 만들때 매번 해줄 수 없으니 디코딩 시에 바로 퍼센트인코딩되도록 프로퍼티 래퍼를 만들었다.


@propertyWrapper
struct PercentEncoded: Decodable {
    private var encodedURL: URL?
    
    var wrappedValue: URL? {
        get { self.encodedURL }
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let urlString = try container.decode(String.self)
        
        if #available(iOS 17, *) {
            self.encodedURL = URL(string: urlString)
        } else {
            if let encodedURLString = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
                self.encodedURL = URL(string: encodedURLString)
            } else {
                self.encodedURL = nil
            }
        }
    }
}

/// 사용 예시
struct Response: Decodable {
    @PercentEncoded var url: URL?
}

iOS 17 추가

iOS 17부터는 URL(string:) 생성자의 동작이 달라졌다. RFC 3986을 따르게 되면서 자동으로 퍼센트인코딩이 된다.
열어보면 내부값이 다른데, UTF-8을 직접 인코딩하는게 아니라 xn--으로 시작하는 Punycode로 인코딩된다.
String으로부터 직접 디코딩하기 때문에 사실 상관없지만 표준에 가깝도록 분기쳐 놓았다. 모두 iOS 17이상을 쓰게되면 그냥 URL타입으로 직접 넣어도 알아서 잘 디코딩될것 같다.


let urlString = "https://네이버.com"
if #available(iOS 17, *) {
    URL(string: urlString) // https://xn--950bt9s8xi.com
} else {
    URL(string: urlString) // nil

    if let encodedURL = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
        URL(string: urlString) // https://%EB%84%A4%EC%9D%B4%EB%B2%84.com
    }
}