티스토리 뷰

Swift-iOS/Swift

URLComponents, URLQueryItem

summercat 2022. 11. 17. 20:18

현재 플젝에 데이터 가져오는 URL이 하드코딩 되어있어서 이걸 내일 리팩토링 하려고 공부해보았다 (사실 구 RayWenderwich의 URLSession 강의 따라하다가 딴길로 샘).

다음과 같은 URL이 있다고 해보자.

https://itunes.apple.com/search?media=music&entity=song&term=Muse

httpsscheme
itunes.apple.comhost
/searchpath
? 뒤부터 나오는 media=music&entity=song&term=Musequery다.

URL을 만들 때마다 위처럼 하드코딩을 해줄수도 있겠지.. 그치만 그런 멍청한 방법은 당연히 쓰고 싶지 않다. 그럼 얘를 어떻게 쪼갤까? 할 때 URLComponents, URLQueryItem을 이용할 수 있다.

URLComponents

URLComponents는 URL을 구성하는 요소들로 URL을 파싱하거나, 그 요소들로부터 URL을 만들어주는 구조체이다.
init 할 수 있는 방법이 총 네 개임.

  1. init()
    • 얘는 그냥 init만 하는 친구. 만약 얘로 위의 URL을 만든다 치면 아래와 같이 만들 수 있다. init?이 아니기 때문에 옵셔널 바인딩 안 해도 됨.
    • 구조체이기 때문에, let으로 선언해버리면 얘의 프로퍼티인 scheme, host 등등에 값을 할당할 수 없으니까, var로 선언해야 한다.
var components = URLComponents()
components.scheme = "https"
components.host = "itunes.apple.com"
components.path = "/search"
components.queryItems = [URLQueryItem(name: "media", value: "music"),
  URLQueryItem(name: "entity", value: "song"),
  URLQueryItem(name: "term", value: "Muse")]
let url = components.url
  • init(from: Decoder)
    • 이건.. 디코더로 파싱한 것을 넣어서 init 하는 것 같다. 근데 얘는 잘 안 쓰나봄. 예제가 없다;
  • init?(from: String)
    • 이건 넣어준 문자열 가지고 init 하는 거. init?이라서 생성되는 URLComponents의 인스턴스가 옵셔널이다. 사용하려면 옵셔널 바인딩 해줘야 함.
let baseUrl: String = "https://itunes.apple.com"

func fetchSearchResults(term: String) {
  var components = URLComponents(string: baseUrl)
  components?.path = "/search"
  components?.queryItems = [
    URLQueryItem(name: "media", value: "music"),
    URLQueryItem(name: "entity", value: "song"),
    URLQueryItem(name: "term", value: "\(term)")
    ]
  guard let url = components?.url else { print("error") return }
}

fetchSearchResults(term: "Muse")
  • init?(url: URL, resolvingAgainstBaseURL: Bool)
    • resolvingAgainstBaseURL: URLComponents가 absolute URL을 써야 하는지를 알려준다. true면 URLComponents가 absolute URL을 갖게 된다.
    • 비교 대상이 될 base URL과 base URL을 기반으로 만든 URL을 정의해주고, base URL을 기반으로 만든 URL(url)을 매개변수로 넣어서 init 해준다.
let base = URL(string: "https://itunes.apple.com")
let url = URL(string: "/search", relativeTo: base)
let url2 = URL(string: "/search")

let absoluteComponents = URLComponents(url: url!, resolvingAgainstBaseURL: true)
print(absoluteComponents?.string)
// Optional("https://itunes.apple.com/search")

let aa = URLComponents(url: url2!, resolvingAgainstBaseURL: true) print(aa?.string)
// Optional("/search")

let relativeComponents = URLComponents(url: url!, resolvingAgainstBaseURL: false)
print(relativeComponents?.string)
// Optional("/search")

let bb = URLComponents(url: url2!, resolvingAgainstBaseURL: false)
print(bb?.string)
// Optional("/search")

맨 위의 그냥 init()한 예제에서 볼 수 있듯, URLComponents는 URL의 구성요소들(scheme, host, query, ...)를 모두 프로퍼티로 갖고 있다.

var scheme: String?
var user: String?
var host: String?
var port: Int?
var path: String
var query: String?
var queryItems: [URLQueryItem]?
var fragment: String?
var password: String?

타입이 String 또는 String?인 애들은 .append도 가능하다.

var components = URLComponents(string: "https://itunes.apple.com")
components?.path.append("/search")

let media = URLQueryItem(name: "media", value: "music")
let entity = URLQueryItem(name: "entity", value: "song")
let term = URLQueryItem(name: "term", value: "Muse")

components?.queryItems = [media, entity, term]

guard let url = components?.url else { return }

쿼리의 경우 query를 사용할 수도 있고, queryItems를 사용할 수도 있다.

  • query를 사용한다면
    • components?.query = "media=music&entity=song&term=Muse"와 같이 문자열로 직접 쿼리 전체를 넣어줄 수 있다.
    • var query: String? { get set }으로 정의되어 있고, 만약 percent encoding이 포함되어 있으면 getter에서 percent encoding을 다 빼버린다고 한다.
    • 이 프로퍼티를 설정하면, subcomponent나 component string이 percent encoding 되어있지 않으며, percent encoding을 추가할 것이라는 것을 의미한다.

Percent-Encoding/Decoding이란?
"https://example.com/music/bands/AC/DC"와 같은 URL을 만들고 싶을 때, /는 path를 구분하는 역할이 있는데, AC/DC를 쓰고 싶으면 어떻게 써야할까? (비슷한 예시로 쿼리에 쓰이는 ? 같은 것들이 있겠다.)
이럴 때 문자로 쓰고 싶은/를 인코딩해서 path를 나누는 /와 구분해야 하는데, URL은 Percent-Encoding 방식을 사용한다. %XX와 같은 형태로 표현하는데, XX에는 넣고 싶은 문자의 ASCII value를 16진수로 넣는다. 예를 들어 /를 인코딩 하는 경우, /의 ASCII value는 16진수로 표현하면 2F이기 때문에 %2F로 표현한다. 그래서 위의 URL을 Percent-Encoding하면 "https://example.com/music/bands/AC%2FDC"가 된다.
인코딩 하는 방법은 여기여기를 참조해보자.

  • queryItems를 사용한다면
    • var queryItems: [URLQueryItem]? { get set }으로 정의되어 있고,
      배열 안의URLQueryItem의 순서 = 쿼리 문자열에 들어갈 순서 이다.
    • 각 URLQueryItem의 name은 unique하다고 보장할 수 없다 (중복으로 등장할 수 있다).
    • URLComponents가 빈 query component를 가지고 있다면 빈 배열을 반환하고,
      query component를 하나도 가지고 있지 않다면 nil을 반환한다.

URLQueryItem

URL의 Query 부분의 한 쌍의 name-value pair. 얘도 구조체로 구현되어 있다.
init(name: String, value: String?)이라서, namevalue는 둘 다 문자열 타입을 넣어주면 된다.

참고링크

Apple Developer - URLComponents
RayWenderwich URLSession Tutorial
Swift by Sundell - Constructing URLs in Swift
Stackoverflow - resolvingAgainstBaseURL
Percent-Encoding and Decoding
URL 처리 방법

'Swift-iOS > Swift' 카테고리의 다른 글

[Swift] Class vs Struct (feat. 메모리 구조, Array, CoW)  (1) 2022.09.22
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함