[iOS] URLSession

Charlie·2022년 9월 25일
0
post-custom-banner

OverView

URLSession은 URL을 통해 데이터를 다운로드, 업로드를 할 수 있는 API를 제공하는 클래스이다.
제공하는 API들을 통해 앱이 background 상태이거나 실행되지 않는 상황, suspended 된 상황에서도 다운로드가 가능하다.

suspended : background 상태에서 작업이 없는 경우

authentication과 완료가 되었을 때 등의 이벤트를 전달 받기 위해 'URLSessionDelegate', 'URLSessionTaskDelegate' 등을 함께 사용할 수 있다.

URLSessionConfiguration을 통해 URLSession을 생성하고 URLSession을 통해 URLSessionDataTask를 생성하여 서버와 통신하고 Delegate을 통해 네트워크의 과정을 확인한다.

Alamofire, Moya 등의 네트워크 라이브러리가 있지만 모두 URLSession을 wrapping한 것이므로 swift에서 HTTP를 이용한 네트워크 사용 방법에 대해 제대로 알고 오류나 특정 로그에 대해 처리하려면 URLSession을 알고 있는 것이 좋다.

URL Sessions 종류

하나의 URL Session으로 만들어진 tasks들은 공통된 configuration을 공유하게 된다. 동시에 최대 연결 가능한 숫자, 셀룰러 데이터를 사용하여 통신을 하는지 등의 configuration 등.

URLSession은 'shared'라는 singleton session을 가지고 있다. 'shared'는 configuration object가 없기 때문에 커스텀 할 수가 없고 따라서 기본적인 요청을 할 때 사용하면 된다. 다른 종류의 세션을 만들기 위해서는 개발자가 URLSession을 다음과 같은 종류의 configuration을 가지는 것으로 만들면 된다.

  • 'default session' : 디스크에 데이터를 기록한다. 순차적으로 데이터를 처리하기 위해 delegate도 지정 가능하다.
  • 'ephemeral session' : cahces, cookies, credentials을 디스크에 작성하지 않는다. 메모리에 올려서 세션을 연결하고, 세션 만료 시 데이터가 사라진다. (비공개 세션) (ephemeral : 단 하나의)
  • 'background session' : 별도의 프로세스가 모든 데이터 전송을 처리하여 background에서 업로드, 다운로드가 가능하다

shared session

URL의 내용들을 메모리에 간단히 fetch하기 위해서 사용하면 된다.
다른 타입의 session들과는 다르게 shared session은 생성하지 못하고 직접 접근할 수도 없어서 delegate이나 다른 configuration을 설정할 수 없다.
따라서 다음과 같은 한계들이 있다.

  • 서버에서 오는 데이터들을 점진적으로 얻을 수 없다.
  • connection behavior를 커스텀할 수 없다.
  • authentication 관련해서 제한적이다.
  • background 상태에서 업로드, 다운로드를 할 수 없다.

캐시, 쿠키, authentication 또는 커스텀 네트워크 프로토콜을 사용하여 어떤 것을 하려면 shared session을 사용하면 안되고 다른 타입의 session을 사용해야 한다.

let sharedSession = URLSession.shared()
let defaultSession = URLSession(configuration: .default)
let ephemeralSession = URLSession(configuration: .ephemeral)
let backgroundSession = URLSession(configuration: .background)

URLSessionTask

각 세션 내에서 작업을 추가할 수 있다.
url 주소만으로 요청할 때에는 URL 객체를 이용하고, 주소와 HTTP메소드, body까지 설정해야 할 때는 URLRequest 객체를 이용한다.

URL

let url = URL(string: "https://test.url.com")

URLRequest

let url = URL(string: "https://test.url.com")
let request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Accept")

URL Session Task 종류

URLSession API는 4가지 종류의 taks를 제공한다.

  • 'Data Task' : NSData 객체를 사용하여 데이터를 보내거나 받는다. 서버에 짧고 자주 요청을 보내야 할 때 주로 사용한다.
  • 'Upload Task' : data task와 비슷하다. 그러나 주로 파일의 형태로도 데이터를 보낼 수 있고, background 업로드도 지원한다.
  • 'Download Task' : file의 형태로 데이터를 받는다. 그리고 background 상태에서 업로드와 다운로드를 지원한다. (Upload도 지원하네..?)
  • 'WebSocket Task' : TCP와 TLS를 통해 메시지를 교환한다. 소켓 프로토콜은 'RFC 6455'에 정의되어 있다.

Task

앞서 생성한 세션의 함수 호출을 통해 task를 추가할 수 있다.
task는 URL 또는 URLRequest 객체 기반으로 데이터를 요청하고, 요청에 대한 응답을 처리할 completionHandler가 필요한 경우 추가로 구현해주면 된다.

DataTask

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

DownloadTask

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

UploadTask

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

Task 제어

  • cancel() : 작업을 취소
  • resume() : 일시중지된 작업 다시 실행
  • suspend() : 작업 일시중지
  • state(get-only) : 작업 상태
  • priority(get-only) : 작업의 우선순위 (0.0 ~ 1.0)

Session Delegate

session 안의 tasks들은 delegate 또한 공유하게 된다. delegate는 다양한 이벤트가 일어났을 때 정보를 얻기 위해 구현한다. 발생할 수 있는 이벤트들로는

  • 인증 실패
  • 서버로부터 데이터 수신
  • 데이터가 캐시할 수 있는 상태가 됨
    가 있다.
    만약 이러한 기능들이 필요가 없다면 session을 생성할 때 nil을 할당하면 된다.

session object는 앱이 꺼지거나 명시적으로 session을 없애기 전까지 delegate와 강한 참조를 유지하게 되기 때문에 session을 명시적으로 없애지 않으면 메모리 릭이 일어난다.

URL을 구성하는 요소들

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"

URLComponents

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)

URL Session과 비동기 작업

다른 네트워크 API들과 마찬가지로 URLSession이 제공하는 API들도 비동기적이다.

  • async 키워드 사용
  • completion handler 사용
  • callback 사용

Parsing

네트워크에서 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"
    }
}

Reference

Apple Developer Documentation
김종권님 블로그
Swift: URL 관련 기능 요약
슈프림님 블로그

profile
Hello
post-custom-banner

0개의 댓글