오늘은 api 호출과 함께 Pagination하는 방법에 대해 알아보겠습니다!
UITableView를 구현해서, api 호출 결과를 띄우는데,
아래로 쭉 스크롤할 때, 스크롤 마지막에 가서 데이터를 추가!함으로써 무한으로 스크롤할 수 있게 말입죠
페이지네이션은 데이터를 나누어 로드해서 유저에게 한 번에 너무 많은 데이터를 쏟아내지 않고, 필요한 만큼만 보여주는 기법입니다.
→ 따라서 Pagination은 대량의 데이터를 효율적으로 로드하고, UX를 향상시키기 위해 필수적입니다.
오프셋 기반 페이지네이션은 데이터를 특정 위치(offset)부터 일정 개수만큼 가져오는 방식입니다.
예를 들어, 데이터 100개가 있을 때, 한 번에 20개씩 가져오려면,
오프셋 기반 페이지네이션은, 비교적으로 구현이 간단합니다.
데이터가 일정한 순서대로 정렬되어 있고, 데이터의 양이 적을 때 유용하구요!
근데 데이터의 양이 많아지면, 높은 오프셋 값으로 인해 쿼리 성능이 저하될 수 있습니다.
커서 기반 페이지네이션은 현재 페이지의 마지막 항목에 대한 참조(커서)를 사용하여, 다음 페이지의 데이터를 가져오는 방식입니다.
→ 각 요청 시 서버는 다음 페이지를 가져오기 위한 커서 반환
대량의 데이터가 있을 때, 성능이 뛰어납니다.
특히 데이터가 자주 변경되는 환경에서 유용합니다.
오프셋 기반 방식과 달리, 새로운 데이터가 추가되어도 페이지가 영향을 받지 않습니다. 따라서 더 안정적이고, 일관적입니당
func fetchData(cursor: String?) {
var urlString = "https://api.example.com/data"
if let cursor = cursor {
urlString += "?cursor=\(cursor)"
}
let url = URL(string: urlString)!
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
// JSON 파싱 및 데이터 추가 처리
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
let newCursor = json?["next_cursor"] as? String
DispatchQueue.main.async {
// 테이블 뷰 업데이트
self.currentCursor = newCursor
}
}.resume()
}
// 데이터 요청 예제
var currentCursor: String?
func loadMoreData() {
fetchData(cursor: currentCursor)
}
예를 들어, UITableView에 이미지가 많으면 빠르게 스크롤할 때 모든 이미지를 다 받아오느라 앱이 느려질 수 있습니다. 이를 해결하기 위해 iOS 10부터 제공하는 Prefetching 프로토콜을 사용할 수 있습니다.
Prefetching 프로토콜이란?
Prefetching 프로토콜은 TableView가 화면에 표시될 셀을 미리 준비할 수 있도록 도와줍니다. 이를 통해 스크롤 성능이 향상되고, 사용자가 더 부드럽게 스크롤할 수 있습니다. 이 방법은 페이지네이션이라기보다는 사용자 경험을 향상시키기 위한 최적화 방법입니다.
어떻게 동작하나?
미리 로딩: TableView는 사용자가 스크롤을 시작하기 전에 곧 표시될 셀의 데이터를 미리 요청합니다. 즉, 사용자가 그 셀을 보기도 전에 데이터를 준비해 놓는 것이죠!!! 언제 요청할지에 대한 기준만 정해주면 됩니다!
중간 취소: 사용자가 아주 빠르게 스크롤하면, TableView는 이미지를 다 받아오는 대신, 필요 없는 부분은 요청을 취소합니다. 이렇게 하면 불필요한 네트워크 요청을 줄이고, 중요한 데이터에 집중할 수 있습니다
이거랑 페이지네이션이랑 뭐가 다른가?
페이지네이션은 데이터를 일정한 크기로 나누어 단계적으로 로드하는 방식이다.
Prefetching은 스크롤할 때 미리 데이터를 로드해서 스크롤이 끊기지 않게 하는 방식이다.
😴 그러니깐, !!
페이지네이션은 대량의 데이터를 단계적으로 가져와서, 클라이언트와 서버 모두의 부담을 줄여준다. 데이터베이스에서 대량의 데이터를 다루는 경우, 한 번에 모든 데이터를 로드하기 부담스러운 경우에 사용하기 좋다.
테이블뷰의 Prefetching은 스크롤 UX를 향상시키기 위함이다. 이미지나 영상처럼 로딩 시간이 많이 필요한 데이터가 많은 리스트나, 스크롤 성능을 최적화하고, 끊김 없는 UX를 주고 싶을 때 사용한다.
페이지네이션과 Prefetching은 그 목적과 장점이 다르기 때문에, 선택해서 사용하거나 둘 다 써도 좋겠다!!
예를 들어서, 쇼핑 앱에서 수백 개, 수천 개의 상품을 표시해야 하는 상황에서는
1) 페이지네이션: 서버에서 데이터를 페이지 단위로 요청(한 번에 20개만 요청 + 스크롤 할 때 추가 데이터 요청)
2) Prefetching: 사용자가 빠르게 스크롤할 때에도 부드럽게 스크롤할 수 있도록, 이미지 미리 로드 (15번째 상품 보고 있을 때, 16~20번째 이미지 미리 로드)
사용할 API에 대한 정보는 다음과 같습니다.
위 API에서 People 값 중 이미지랑 이름 정도만 받아와서, UITableView에 넣어보겠습니다.
저는 TableView의 Prefetching 프로토콜과 오프셋 기반의 페이지네이션을 사용해서 구현해볼겁니다.
가져온 데이터는 peopleSubject에 추가하고, currentPage를 증가시켜 다음 페이지!
private var peopleSubject = BehaviorSubject<[People]>(value: [])
//--------
private func fetchMorePeople() {
fetchTVPeople(page: self.currentPage, group: nil) { result in
switch result {
case .success(let success):
self.peopleList.append(contentsOf: success)
self.currentPage += 1
self.peopleSubject.onNext(self.peopleList)
case .failure(let failure):
self.peopleSubject.onError(failure)
}
self.isLoading = false
}
}
extension TVViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
guard let maxRow = indexPaths.map({ $0.row }).max() else { return }
if maxRow > people.count - 10 {
viewModel.apiRequest()
}
}
}