URLSession은 URL을 통해 데이터를 다운로드, 업로드를 할 수 있는 API를 제공하는 클래스이다.
제공하는 API들을 통해 앱이 background 상태이거나 실행되지 않는 상황, suspended 된 상황에서도 다운로드가 가능하다.
suspended : background 상태에서 작업이 없는 경우
authentication과 완료가 되었을 때 등의 이벤트를 전달 받기 위해 'URLSessionDelegate', 'URLSessionTaskDelegate' 등을 함께 사용할 수 있다.
URLSessionConfiguration을 통해 URLSession을 생성하고 URLSession을 통해 URLSessionDataTask를 생성하여 서버와 통신하고 Delegate을 통해 네트워크의 과정을 확인한다.
Alamofire, Moya 등의 네트워크 라이브러리가 있지만 모두 URLSession을 wrapping한 것이므로 swift에서 HTTP를 이용한 네트워크 사용 방법에 대해 제대로 알고 오류나 특정 로그에 대해 처리하려면 URLSession을 알고 있는 것이 좋다.
하나의 URL Session으로 만들어진 tasks들은 공통된 configuration을 공유하게 된다. 동시에 최대 연결 가능한 숫자, 셀룰러 데이터를 사용하여 통신을 하는지 등의 configuration 등.
URLSession은 'shared'라는 singleton session을 가지고 있다. 'shared'는 configuration object가 없기 때문에 커스텀 할 수가 없고 따라서 기본적인 요청을 할 때 사용하면 된다. 다른 종류의 세션을 만들기 위해서는 개발자가 URLSession을 다음과 같은 종류의 configuration을 가지는 것으로 만들면 된다.
URL의 내용들을 메모리에 간단히 fetch하기 위해서 사용하면 된다.
다른 타입의 session들과는 다르게 shared session은 생성하지 못하고 직접 접근할 수도 없어서 delegate이나 다른 configuration을 설정할 수 없다.
따라서 다음과 같은 한계들이 있다.
캐시, 쿠키, authentication 또는 커스텀 네트워크 프로토콜을 사용하여 어떤 것을 하려면 shared session을 사용하면 안되고 다른 타입의 session을 사용해야 한다.
let sharedSession = URLSession.shared()
let defaultSession = URLSession(configuration: .default)
let ephemeralSession = URLSession(configuration: .ephemeral)
let backgroundSession = URLSession(configuration: .background)
각 세션 내에서 작업을 추가할 수 있다.
url 주소만으로 요청할 때에는 URL 객체를 이용하고, 주소와 HTTP메소드, body까지 설정해야 할 때는 URLRequest 객체를 이용한다.
let url = URL(string: "https://test.url.com")
let url = URL(string: "https://test.url.com")
let request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession API는 4가지 종류의 taks를 제공한다.
앞서 생성한 세션의 함수 호출을 통해 task를 추가할 수 있다.
task는 URL 또는 URLRequest 객체 기반으로 데이터를 요청하고, 요청에 대한 응답을 처리할 completionHandler가 필요한 경우 추가로 구현해주면 된다.
func dataTask(with url: URL) -> URLSessionDataTask
func dataTask(with request: URLRequest) -> URLSessionDataTask
func dataTask(with url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
func dataTask(with request: URLRequest, completion: @escaping (Data?, URLResponse?m Error?) -> Void) -> URLSessionDataTask
func downloadTask(with url: URL) -> URLSessionDownloadTask
func ddownloadTask(with request: URLRequest) -> URLSessionDownloadTask
func downloadTask(with url: URL, completion: @escaping (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask
func downloadTask(with request: URLRequest, completion: @escaping (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask
func uploadTask(with request: URLRequest, from bodyData: Data) -> URLSessionUploadTask
func uploadTask(with request: URLRequest, fromFile fileURL: URL) -> URLSessionUploadTask
func uploadTask(with request: URLRequest, from bodyData: Data?, completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask
func uploadTask(with request: URLRequest, fromFile fileURL: URL, completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask
session 안의 tasks들은 delegate 또한 공유하게 된다. delegate는 다양한 이벤트가 일어났을 때 정보를 얻기 위해 구현한다. 발생할 수 있는 이벤트들로는
session object는 앱이 꺼지거나 명시적으로 session을 없애기 전까지 delegate와 강한 참조를 유지하게 되기 때문에 session을 명시적으로 없애지 않으면 메모리 릭이 일어난다.
let urlString = "https://itunes.apple.com/search?media=music&entity=musicVideo&term=collier"
let url = URL(string: urlString)
url?.absoluteString // 절대 주소 (urlString과 동일함)
url?.scheme // http 또는 https 등의 통신 방법
url?.host // 메인 주소. "itunes.apple.com"
url?.path // host 뒤에 query parameter를 제외한 주소. "/search"
url?.query // query parameter 값. "media=music&entity=musicVideo&term=collier"
url?.baseURL // 설정하지 않으면 디폴트는 nil
let baseURL = URL(string: "https://itunes.apple.com")
let relativeURL = URL(string: "/search?media=music&entity=musicVideo&term=collier", relativeTo: baseURL)
url?,baseURL // 설정한 이후 baseURL 확인 가능해짐. "https://itunes.apple.com"
URL에 포함되어 있는 한글, 띄어쓰기 등을 자동으로 인코딩하여 관리한다.
URLComponents를 사용하면 URL을 사용하는 것에 비해 query parameter 부분을 URLQueryItem을 이용하여 쉽게 추가할 수 있다.
var urlComponents = URLComponents(string: "https://ios-development.tistory.com/search/users?")
// URLQueryItem를 이용하여 query를 설정
let userIDQuery = URLQueryItem(name: "id", value: "123")
let ageQuery = URLQueryItem(name: "age", value: "20")
urlComponents?.queryItems?.append(userIDQuery)
urlComponents?.queryItems?.append(ageQuery)
/// 전체 경로 (문자열이 아닌 형태) https://ios-development.tistory.com/search/users?id=123&age=20
print(urlComponents?.url)
/// "https://ios-development.tistory.com/search/users?id=123&age=20"
print(urlComponents?.string)
/// [id=123, age=20]
print(urlComponents?.queryItems)
다른 네트워크 API들과 마찬가지로 URLSession이 제공하는 API들도 비동기적이다.
네트워크에서 JSON파일을 받아오기 위해서는 Codable 프로토콜을 채택하는 struct를 구현하여 parsing할 수 있다. Codable은 Decodable과 Encodable이 합쳐진 프로토콜로 인코딩과 디코딩을 가능하게 해준다.
Codable을 구현한 struct를 만들 때 구조체 내부의 변수 이름과 JSON의 변수 이름과 동일하게 맞추는 것이 중요하다.
만약 이름을 다르게 해야 한다면 CodingKey
라는 열거형을 생성해서 변수 이름을 매핑할 수 있따.
struct Response: Codable {
let resultCount: Int
let tracks: [Track]
enum CodingKeys: String, CodingKey {
case resultCount
case tracks = "results"
}
}
struct Track: Codable {
let title: String
let artistName: String
let thumbnailPath: String
enum CodingKeys: String, CodingKey {
case title = "trackName"
case artistName
case thumbnailPath = "artworkUrl100"
}
}
Apple Developer Documentation
김종권님 블로그
Swift: URL 관련 기능 요약
슈프림님 블로그