URL Session

봄이아빠·2024년 12월 9일
1

UIKit

목록 보기
4/5


api에서 전달해주는 데이터가 위와 같을 때,
먼저 해당 api와 동일한 구조로 데이터 구조를 만들어야 한다.
그리고 해당 구조를 decode(type, from)의 type에 넣어주어야 에러 없이 api가 주는 데이터를 받아올 수 있다.

단, 모든 필드를 만들어야 하는 것은 아니고 만일 id만 필요하다면

struct UserId: Codable{
	let id: Int
}

위와 같은 구조체를 정의하여 id만 받아올 수 있다.

받아올 데이터에 맞는 구조체를 정의했다면 메서드를 작성하여 데이트럴 받아오면 된다.
아 혹시라도 줄지 안 줄지 모르겠다면 세부 타입을 옵셔널로 처리해줄 수 있다.


URLSession

let urlSession = URLSession(configuration: .default)

URLSession은 ios에서 HTTP 요청을 관리하고 데이터 송수신을 처리하는 객체.

매개변수 configuration의 주요 옵션 세 가지는 다음과 같다.

  • .default: 기본 캐시와 쿠키 저장소를 사용하는 일반적인 세션.
  • .ephemeral: 캐시나 쿠키를 저장하지 않는 임시 세션.
  • .background: 앱이 백그라운드 상태에서도 작업을 계속 수행할 수 있는 세션.

URLSession은 공유 세션이 존재하여 간단한 요청은 처리도 간단하게 할 수 있도록 도와준다.
그 외의 일반적인 사용은 configuraion을 .default로 설정하여 기본 세션을 사용한다.
이 외에 타임 아웃을 설정하거나 백그라운드 다운로드 등을 설정하기 위해서는 직접 커스텀해야 하는데 다음 코드처럼 사용할 수 있다.

let configuration = URLSessionConfiguration.default//default 세션
configuration.timeoutIntervalForRequest = 30 //타임 아웃 설정
let urlSession = URLSession(configruation: configuration)
//타임 아웃을 설정한 configuration 설정

URLSession은 비동기로 동작하기 때문에 요청이 완료된 후엔 클로저 콜백을 통해 작업을 처리해야 한다.

사용 흐름을 정리하자면
URL을 생성하고 dataTask 등의 작업도 생성한다.
.resume()을 호출하여 작업을 실행하고 클로저를 통해 데이터, 응답, 에러에 관한 처리를 수행한다.

dataTask를 통해 받는 데이터 중 error에 관한 부분은 주로 다음과 같이 접근하여 사용한다.

error.localizedDescription

URLSessionTask

세션이 수행할 구체적인 네트워크 작업들로
DataTask, DownloadTask, UploadTask, StreamTask 등이 있다.
이번에 사용할 것은 DataTask로 데이터를 요청하고 응답 데이터를 메모리에 저장하며 주로 API호출에 사용된다.

주요 특징으로는
작업은 비동기로 백그라운드에서 수행되며 요청 완료 후에 Completion Handler라는 클로저를 통해 응답을 전달한다.

with 파라미터에는 간단한 GET요청을 위해 URL을 바로 사용할 수 있고
요청을 세부적으로 설정할 때는 URLRequest를 설정하여 전달해줄 수도 있다.
이번에 들은 강의에서는 다음과 같이 URLRequest를 설정했다.

var request: URLRequest = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")

URLRequest

URLRequest로 잠시 빠져보겠다.
기본적으론 URL을 기반으로 생성한다.

var request = URLRequest(url:)

그리고 메서드나 헤더 등을 설정할 수 있다.
HTTP메서드는 기본값을 GET으로 가지고 있으며 CRUD에 맞는 POST, GET, PUT, DELETE등을 String으로 넣어준다.

addValue메서드를 통해 헤더도 설정해줄 수 있는데
HTTP헤더란 서버와 클라이언트 간의 request와 response 메시지에 포함된 추가적인 메타데이터이다.
(addValue로 헤더 이름에 대한 값을 추가하는 대신 setValue를 통해 값을 대체해버릴 수도 있다.)
api와 통신하기 위한 헤더와 값은 서버가 요구사항에 따라 정의되고 정해진 규칙에 따라 요청해야 올바르게 처리될 수 있다.
해당 규칙은 url만으론 알 수 없고 API문서나 서버의 요구사항을 따로 확인해야 한다.


URLSessionTask

다시 Task로 돌아와서 completionHandler를 살펴보자면
해당 클로저로 전달 되는 정보는 data, response, error가 있다.

data는 서버로부터 반환된 데이터다.
요청 실패를 가정하여 옵셔널로 받고 주로 JSON이나 HTML 형식으로 돌아온다.
즉 다시 디코딩이 필요하다.

response는 HTTP 응답 정보이며 주로 HTTPURLResponse로 캐스팅하여 상태코드와 헤더 정보를 확인한다.
여기서 상태코드란 요청의 결과를 나타내는 3자리의 숫자로 범위에 맞는 의미가 정해져있다.
(그 유명한 404 not found도 상태코드)

Status code
  • 1xx: 정보 응답
    • 요청을 수신했으며 추가 작업 진행 중
      • ex) 100 Continue: 클라이언트가 요청을 계속 진행해도 됨
  • 2xx: 성공
    • 요청이 성공적으로 처리됨
      • ex) 200 OK: 요청이 성공적으로 처리 됨
      • ex) 201 Created: 리소스가 성공적으로 생성됨
  • 3xx: 리다이렉션
    • 요청한 리소스가 이동되었으며, 클라이언트는 다른 URL로 요청해야 함
      • ex) 301 Moved Permanently: 리소스가 영구적으로 이동 ㅗ딤
      • ex) 302 Found: 리소스가 임시로 다른 URL에 있음
  • 4xx: 클라이언트 오류
    • 요청이 잘못되었거나, 클라이언트의 권한 부족
      • ex) 400 Bad Request: 요청 구문이 잘못되거나 파라미터가 틀림
      • ex) 401 Unauthorized: 인증이 필요하거나 인증 실패
      • ex) 403 Forbidden: 요청이 허용되지 않음
      • ex) 404 Not Found: 요청한 리소스가 존재하지 않음
  • 5xx: 서버 오류
    • 서버에서 요청을 처리하지 못함.
      • ex) 500 Internal Server Error: 서버에서 일반적인 오류 발생.
      • ex) 503 Service Unavailable: 서버가 현재 요청을 처리할 수 없음.

error는 요청 실패 시 에러 정보를 담고 성공 하면 nil이 담긴다.

이렇게 총 3개의 정보를 가지고 디코딩이나 에러를 처리하는 클로저를 작성한다.

urlSession.dataTask(with: request) { data, response, error in
	let successRange: Range = (200..<300) //status code가 요청 성공에 해당하는 범위
	//data가 존재하는지, error가 존재하는지 체크
    guard let data, error == nil else {
    	print("error: \(error?.localizedDescription ?? "Unknown error")")//error설명이 있다면 띄우고 없을 때의 예외처리까디
      	return
	}
    if let response = response as? HTTPURLResponse {//타입 캐스팅
		//status code 출력
		print("statusCode: \(response.statusCode)")
        //status 코드가 범위 내에 들어가는지 체크
        if successRange.contains(response.statusCode) {
        	do {//받아온 data를 원하는 타입으로 디코딩 시도
            	//JSONDecoder()는 객체를 생성하여 날짜 처리나 키 변환 전략 등을 직접 조정하여 사용하기도 함
            	let data = try JSONDecoder().decode(ResponseData.self, from: data)
                print("data: \(data)")
            } catch {
            	print("error: \(error))
            }
        } else {
        	print("failed with status code: \(response.statusCode)")
        }
	}
}

.resume()

이후 resume()을 호출하여 작업을 실행하게 되는데,
dataTask를 생성하는 즉시 실행되는 것이 아닌 이유는 작업의 명확한 제어와 유연성을 제공하기 위함이다.

이 메서드를 호출 하면 작업이 백그라운드 큐에 에약된다. URLSession은 GCD를 사용해 비동기 작업을 처리하고 완료된 결과는 클로저를 통해 전달받는다.

비동기로 처리하는 덕에 작업을 실행 중에도 UI가 반응성을 유지할 수 있게 해준다.
그 대신 네트워크 요청 완료 후 UI업데이트를 따로 메인스레드에서 수행하도록 보장해주어야 한다.
메인스레드에서 UI를 업데이트 하기 위해선 DispatchQueue.main.async를 사용한다.
해당 큐는 메인 스레드와 연결된 큐로 UI작업이 실행되어야 하는 큐다.
async를 통해 비동기적으로 작업을 예약하여 큐에 추가된 순서대로 작업을 실행할 수 있다.
(네트워크 외에도 무거운 작업 등에서 DispatchQueue.main.asyncDispatchQueue.global(qos:)를 적절히 활용하여 메인 스레드와 백그라운드 스레드를 구분해 작업을 처리)
(* sync는 작업이 완료될 때까지 해당 스레드를 차단하기 때문에 메인 스레드에서 사용할 경우 앱이 멈출 수 있다.)

다시 한 번 정리하자면 네트워크 처리는 백그라운드에서 처리되며 이를 통해 생긴 데이터를 UI업데이트에 반영하려면 반드시 메인 스레드에서 작업하도록 DispatchQueue에 async를 통한 비동기 작업을 예약한다.
다만 불필요하게 자주 호출하면 성능이 저하될 수 있다.

이렇게 네트워크 요청 - 비동기 처리 - 메인 스레드에서 UI업데이트까지의 흐름을 파악해보았다.

추가로

  • 응답 데이터는 파일로 저장하지 않고 메모리에서 직접 처리하는데 만약 다운로드가 필요하다면 DataTask가 아닌 DownloadTask를 사용해야 한다.

  • 터미널에서 jq 플러그인을 설치하면

curl url |jq

를 통해 가장 상단의 사진처럼 개행이 적절히 처리된 데이터를 볼 수 있다.

0개의 댓글