[iOS] URLSession

·2024년 1월 9일

URLSession

  • An object that coordinates a group of related, network data transfer tasks.
    • 관련된 네트워크 데이터 전송 작업 그룹을 조정하는 개체
  • 애플에서 제공하는 네트워크 통신을 위한 API
  • URL의 endpoint에서 데이터를 다운로드 및 업로드할 수 있는 API를 제공

진행 방식은 간략하게 다음과 같음.
1. session configuration을 지정하고 session을 만들기
- session configuration을 따로 만들지 않고 shared session을 사용할 수도 있슴
2. URLRequest 만들기
3. session 내에 task 추가
4. task 실행

지금은 이 흐름이 이해가 안 될 수도 있는데, 하나하나 개념을 알고 나면 충분히 이해될 것임! URLSessionConfiguration 먼저 차근차근 알아가봅시다.


URLSessionConfiguration

  • 통신을 할 때 configuration을 만드는 것이 첫 번째 단계
  • URLSession 객체를 사용하여 네트워크 통신을 할 때 session의 동작과 정책을 정의하는 개체
    • timeout values, caching policies, connection requirement, URLSession을 사용하기 위해 구성하려는 다양한 정보들을 정의
  • session 객체는 프로그래머가 세팅한 configuration의 복사본을 만들어 session을 구성한다

→ 한마디로 일단 서버와 데이터를 주고받으려면 configuration을 만들어야 한다는 것. 근데 난 한번도 configuration을 생성한 적이 없고… 항상 shared를 썼던 것 같은데… 하는 사람들도 많을 것임. 내가 그랬기 때문에 💦

그렇다면 shared session을 포함한 session의 종류들부터 알아보자!

singleton shared session

  • 기본적인 요청을 위한 session
  • 위에서 설명한 configuration 객체나 delegate가 따로 없음
    • delegate는 진행 중인 task에 더 세부적인 처리를 할 수 있도록 하는 방식
  • 제한된 요구 사항일 경우에 사용하기 좋음
  • shared 클래스의 메소드를 호출하여 접근 가능
  • 싱글톤으로 이미 만들어져 있기 때문에 따로 session 생성이 필요 없음. shared에 접근하기만 하면됨.

하지만 shared session만 사용하지 않고, configuration 객체가 존재하는 이유가 있다. shared session에는 몇 가지 제한 사항이 있다고 함.

shared session의 제약 사항

  • 서버로부터 데이터가 도착하면 데이터를 점진적으로 얻을 수 없음
    • 전체 데이터가 도착한 후에 처리할 수 있다
    • shared session에서는 사용할 수 없는 custom delegate를 사용하면 데이터가 조금씩 도착할 때마다 처리할 수 있는 메소드가 있음
      • ex. URLSessionDataDelegateurlSession(_:dataTask:didReceive:)
  • 기본으로 지정되어 있는 네트워크 연결 동작을 커스텀할 수 없음
  • authentication 기능이 제한됨
    • 기본적으로 저장된 cache, cookie, credential로 인증 처리 → 커스텀 불가
  • 앱이 실행되고 있지 않으면, background에서 업로드나 다운로드를 할 수 없음

default session

  • shared session과 매우 유사하지만 delegate를 사용할 수 있음
    • delegate에 대해서는 글을 새로 써보려고 해서 정말 간단하게만 적어보자면

      서버로부터 데이터가 도착하면 데이터를 점진적으로 얻을 수 없음

      아까 shared session에서는 이런 제약 사항이 있다고 했는데, delegate 방식으로 사용하면 task가 실행되는 동안에도 세부적인 처리를 해줄 수 있음

ephemeral session

  • ephemeral - 임시
  • default session과 유사하지만, caches, cookies, credentials을 디스크에 기록하지 않음
    • ex. private browsing mode 같은 것에 적합함

background session

  • 앱이 실행되지 않는 동안 background 상태에서 컨텐츠를 업로드하거나 다운로드할 수 있음

URLSessionTask

우리는 session 내에서 데이터를 주고 받는 task를 생성할 수 있다.

Task Type

  • data task는 NSData 객체를 통해 데이터를 보내고 받음
  • upload task는 data task와 유사하지만, 앱이 실행되지 않을 때도 background 업로드를 지원한다. upload task이지만 데이터를 받을 수도 있음
  • download task는 파일 형식으로 데이터를 검색하고, 앱이 실행되지 않는 동안의 다운로드와 업로드를 지원
  • webSocket task는 RFC 6455에 정의된 WebSocket 프로토콜을 사용하여 TCP와 TLS를 통해 메세지를 교환

URLRequest

  • 서버로 보내는 요청을 설정
  • request를 로드하는 데 필요한 2가지 속성 캡슐화
    - 로드할 URL
    - 이를 로드하는 데 사용되는 정책들(캐시 정책, timeout 간격 등)

    이외에도 여러가지 많음
  • HTTP method(GET, POST 등)이 포함됨
    • 참고로 swift에서 httpMethod 프로퍼티의 기본값은 GET

Practice

나는 현재 우주에 있는 사람 수랑 그 사람들에 대한 정보를 리스트로 보여주는 재밌는 open api가 있길래 요걸 사용했다.

How Many People Are In Space Right Now

서버 통신에 관한 기본적인 내용은 일단은 패스하고!

간단하게 받아오는 데이터 중에서 지금 우주에 몇 명이 있는지만 뷰에 나타내 보려고 한다.

예시에 대한 정보는 대충 요정도일 것 같다.

  • SwiftUI
  • shared session에 data task
  • escaping closure로 비동기 처리
  • mvvm

따라서 그냥 URLSession을 어떻게 사용하는지에만 집중하면 될 듯함!


먼저 data task를 추가하는 코드로 구현하는 방법만 간략하게 보고 가려고 한다.

with

with 파라미터도 URLRequestURL 타입으로 받을 수 있음

내가 통신할 api의 http method는 get 으로 기본값이고, 따로 정의할 게 없어서 그냥 URL 타입으로 넣어줄 예정

completionHandler

completionHandler 클로저는 서버로부터 데이터가 왔을 때 실행됨
따라서, 받아온 데이터를 어딘가에 저장하거나 보여주려면 completionHandler을 써야 한다.
앞서 말했던 것처럼 completionHandler가 아니라 delegate data task를 추가할 수도 있음!
앞부분을 다 이해했으면 알겠지만 나는 shared session을 사용할 것이므로 delegate는 사용할 수 없다.


그럼 예제 ㄱㄱ


1. reponse에 맞게 모델을 만들기

  • api document에 나와있는 모델
{
  "message": "success",
  "number": NUMBER_OF_PEOPLE_IN_SPACE,
  "people": [
    {"name": NAME, "craft": SPACECRAFT_NAME},
    ...
  ]
}
  • 내 코드에서 사용할 수 있도록 response에 맞게 model 제작
struct SpaceResponse: Codable {
    let message: String
    let people: [Person]
    let number: Int
}

struct Person: Codable {
    let name, craft: String
}

2. session을 만들고 task 추가하기

class DataManager {
    func getPeopleNumber(handler: @escaping (_ number: Int?, _ error: Error?) -> ()) {
        let url = "http://api.open-notify.org/astros.json"

        URLSession.shared.dataTask(with: URL(string: url)!) { data, response, error in
            guard let data = data,
                  let response = response as? HTTPURLResponse,
                  response.statusCode == 200 else { return }

            do {
                let decodedResponse = try JSONDecoder().decode(SpaceResponse.self, from: data)
                handler(decodedResponse.number, error)
            } catch {
                print("디코딩 오류 🚨")
            }
        }
        .resume()
    }
}

dataTask(with:completionHandler:)

func dataTask(
    with url: URL,
    completionHandler: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void
) -> URLSessionDataTask

completionHandler 는 요청이 완료됐을 때 실행됨.
completionHandler 의 파라미터를 살펴보자.

data

  • Data? 타입
  • 서버로부터 받는 데이터

Data 타입은 아까 만들어놓은 response 모델로 디코딩해야 사용할 수 있음

JSONDecoderdecode 메서드를 통해 Data 타입 → 제작한 response 모델로 디코딩 가능

response

  • URLResponse? 타입
  • http나 https 요청을 하면 실제로는 HTTPURLResponse 객체로 응답이 온다

    HTTPURLResponseURLResponse를 상속하기 때문에 다운캐스팅 필요

error

요청에 성공하면 nil이, request가 실패했을 때는 Error가 나타남

resume()

suspended(중단된) 상태의 task를 resume(다시 시작)시키는 메소드
resume 을 호출해 주어야 하는 이유는, 새로 만들어진 task들은 중단된 상태로 시작하기 때문임! 따라서 우리가 시작하도록 만들어줘야 함.


3. viewModel과 view에서 메서드 호출

class SpaceNumberViewModel: ObservableObject {
    @Published var peopleNumber: Int?
    let dataManager = DataManager()

    func fetchPeopleNumber() {
        dataManager.getPeopleNumber { [weak self] number, error in
            DispatchQueue.main.async {
                self?.peopleNumber = number
            }
        }
    }
}

viewModel에서 dataManagergetPeopleNumber을 호출하고,
디코딩해서 넘겨받은 number을 viewModel의 Published 프로퍼티에 할당해 줌

SwiftUI가 익숙하지 않은 사람들을 위해 잠깐 쓰자면! @Published 프로퍼티가 있는 객체가 변경될 때마다 해당 객체를 사용하고 있는 뷰들은 다시 로드돼서 반영을 하게 됨.

struct SpaceNumberView: View {
    @StateObject private var viewModel = SpaceNumberViewModel()

    var body: some View {
        VStack {
            Text("우주에는 지금 몇 명이 있을까?")
            Text("\(viewModel.peopleNumber ?? 0)")
        }
        .onAppear {
            viewModel.fetchPeopleNumber()
        }
    }
}

그리고 이렇게 view에서 viewModel의 fetchPeopleNumber을 호출하면 끝!


이렇게 기본적인 shared session을 사용해 보았고, 다음 글에서는 configuration을 구성해서 delegate 방식으로 데이터를 받아와보자!


Apple 공식 문서 - Fetching website data into memory
Apple 공식 문서 - URLSession

0개의 댓글