이전글 에서 JSON 모델을 생성했으니, 이번엔 네트워크에서 JSON 형태의 API를 받아오는 방법을 알아보도록하자.
API 호출
클라이언트 개발의 꽃은 서버에서 API를 Request하고, Response하여 데이터를 주고받는 것이라고 생각한다.
이번 포스트에서는 URLSession이라는 객체를 활용하여 HTTP API를 호출하여 JSON 형태의 데이터를 받아오는 작업을 해 보려 한다.
저번 포스트에서 언급했지만 이 프로젝트에서는 단순히 API를 받아오기만 해볼 예정이다.
URLSession
먼저, 애플에서 기본으로 제공하는 API를 요청하는 데 필요한 기능인 URLSession에 대해 간단히 알아보자.
애플 공식 개발 문서에는 URL로 표시된 엔드포인트에서 데이터를 다운로드하고 엔드포인트에 데이터를 업로드하기 위한 API를 제공하는 NSObject를 상속하는 객체라고 설명이 되어있다.
URLSession은 크게 4가지 종류로 나눌 수 있는데,
- Shared session : 사용자가 커스터마이징을 할 수는 없지만 요구사항이 제한적일 때 사용하기 좋은 싱글톤 세션
- Default session : Shared session와 비슷하지만 직접 구성할 수 있고, 데이터를 점진적으로 획득할 수 있도록 delegate를 할당할 수 있는 세션
- Ephemeral session : Shared session와 비슷하지만 캐시, 쿠키 또는 자격 증명을 디스크에 저장하지 않는 임시 세션
- Background session : 앱이 돌아가지 않는 백그라운드에 있어도 업로드와 다운로드가 가능한 세션
이렇게 네가지 종류로 나눌 수 있다.
Shared session의 장점은
큰 요구사항이 없어 굳이 커스터마이징을 요하지 않고, 1000개 정도 되는 동일한 타입의 데이터를 계속해서 요청할 것이라면,
싱글톤으로 미리 고정적으로 메모리를 할당해 주어 메모리 낭비를 하지 않을 수 있는 장점이 있다.
그치만 캐싱을 활용할 예정이라면 Shared session과 비슷하게 싱글톤으로 구현되어있지만, 커스터마이징이 되는 Default session을 활용하는 것을 추천한다.
물론 싱글톤의 특성 상,
전역에서 접근이 가능해 많은 곳에서 참조될 경우 다른 인스턴스들과 결합도가 높아져 수정과 테스트가 힘들어지는 구조를 가지게 될 가능 성이 높다는 단점이 있긴 하지만 일단은 넘어가보겠다.
이런 URLSession 작업 유형은 크게 4가지 유형이 있는데,
- Data tasks : 짧고 빈번한 통신에 사용하는 NSData 객체를 사용하여 데이터를 주고받는 작업
- Upload tasks : 파일 형식으로 데이터를 업로드하는 작업 (앱이 실행되지 않는 동안에도 백그라운드 업로드를 지원)
- Download tasks : 파일 형식으로 데이터를 검색하는 작업 (앱이 실행되지 않는 동안에도 백그라운드 다운로드 및 업로드를 지원)
- WebSocket tasks : RFC 6455에 정의된 WebSocket 프로토콜을 사용하여 TCP 및 TLS를 통해 메시지를 교환하는 작업
그때그때 상황에 맞는 작업을 선택하여 하면 되겠지만, 이 프로젝트에서는 가장 간단한 데이터 작업만 다뤄볼 예정이다.
또한, 대부분의 네트워킹 API와 마찬가지로 URLSessionAPI는 비동기적으로 동작한다.
전송이 완료될 때 실행되는 Completion Handler 블록을 통해 에러나 데이터를 리턴받고,
전송이 진행되는 동안과 전송이 완료된 직후에 데이터를 델리게이트 메서드를 통해 현재 상태와 진행 현황에 대한 콜백을 받을 수 있다.
물론, 이 모든 과정에서 URLSessionAPI는 thread safety하다.
델리게이트 메서드들이 컴플리션 핸들러를 호출하면 태스크는 자동으로 델리게이트 큐에 스케쥴링 된다.
Request Data
그럼 URLSession에 대한 간략한 소개는 이 정도로 하고, 실제로 코드에 어떻게 적용시키는 지를 알아보자.
먼저, 받아올 페이지의 URL을 선언하고 URLSession에 작업을 부여해보자.
final class DataManager {
func performRequest(with urlString: String, completion: @escaping ([ImageData]?) -> Void) {
// URL 구조체 선언
guard let url = URL(string: urlString) else { return }
// URLSession 생성
URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in
if error != nil {
completion(nil)
return
}
guard let safeData = data else {
completion(nil)
return
}
// 데이터 분석하기
if let imageData = self.parseJSON(safeData) {
completion(imageData)
} else {
completion(nil)
}
}.resume() // 일시정지된 상태로 작업이 시작하기 때문에 시작 신호 호출
}
}
간단하게 만들자면 이런 형태가 되겠다.
물론 옵셔널에 대한 에러처리, 네트워킹 에러에 대한 에러처리 등은 하나도 구현하지 않았다.
이건 차차 하나씩 채워 넣어 가도록 하고, 잘 동작하는지 한번 확인해보자.
뭔가 엄청 많은 데이터들이 받아와 지긴 했다.
Lorem Picsum 사이트에 가서 어떻게 된 현상인지 한번 알아보자.
API는 기본적으로 한 페이지당 30개의 아이템을 반환한다고 적혀있다.
그럼 쿼리를 살짝 변경시켜서 한 페이지에 받는 아이템을 제한하고, 페이지를 체크할 수 있는 함수를 한번 만들어보자.
final class DataManager {
let imageURL: String = "https://picsum.photos/"
var PageNo: Int = 1
var pageLimit: Int = 5
func fetchImage(completion: @escaping ([ImageData]?) -> Void) {
let urlString = "\(imageURL)v2/list?page=\(PageNo)&limit=\(pageLimit)"
performRequest(with: urlString) { imageData in
completion(imageData)
}
}
}
이런 방식의 함수를 통해 페이지와 페이지당 아이템 수를 설정할 수 있다.
물론 이런식으로 함수를 짜면 의존성이 상당히 높아지지만, 이 문제 또한 추후에 다시 리팩토링 할 기회를 가져보자.
그럼 다시 잘 동작하는지 한번 빌드해보도록하자.
데이터 개수를 확인해 보면 아까 제한했던 한 페이지에 5가지 아이템만 받아온 것을 확인 할 수 있다.
이런식으로 쿼리를 통해 한번에 보낼 데이터 제한을 서버에서 주면 프론트에서는 편하게 작업할 수 있긴한데,
이 방법은 서버에서 쿼리를 어떻게 내려주냐에 따라 다르기 때문에 Lorem Picsum 사이트에서만 사용하는 방식이라 생각하면 된다.
이렇게 네트워킹을 통해 데이터를 받아오는 방법을 알아보았는데,
다음 포스팅에서는 이미지 캐싱에 대해 알아보고 코드를 구현해보는 작업을 포스팅 할 예정이다.
이 포스트의 프로젝트는 여기서 확인할 수 있습니다.
https://github.com/hminkim/iOSSandbox
오늘 작업 내용은 여기서 확인할 수 있습니다.
https://github.com/hminkim/iOSSandbox/pull/8
'ToyProject' 카테고리의 다른 글
[Project] Swift에서 JSON 파싱하는 법 (1) | 2023.10.28 |
---|---|
[Project] GitHub Issue / PR 템플릿 등록하는 법 (0) | 2023.10.26 |
[Project] Xcode로 GitHub 연동 및 Repository 세팅 (0) | 2023.10.26 |