1. 기능을 구현한 이유
커뮤니티 앱을 개발하면서, 게시글 조회 API를 통해 데이터를 효과적으로 처리하고 화면에 표시하는 것이 필요했습니다. 대규모 데이터를 한 번에 표시하게 되면 사용자 경험이 저하되고 성능에도 영향을 미칠 수 있습니다.
따라서, 두 기능을 도입해 네트워크 오버헤드를 줄이고 초기 데이터 로딩 시간을 단축시킬 수 있도록 구현하였습니다.
1-1. Pagination (페이지네이션)
- 정보를 여러 페이지로 나누어 표시하는 프로세스
- 사용자가 스크롤할 때, 다음 페이지의 데이터를 가져와 표시
1-2. Prefetching (프리패칭)
- 데이터가 실제로 필요하기 전에 사전에 데이터를 가져오고 캐시에 저장하는 프로세스
- 사용자가 스크롤할 때, 미리 데이터를 로드
2. Pagination 방식과 장단점
구현 방법으로는 크게 2가지 방식이 있습니다. 사용하는 상황에 따라 장단점이 존재하므로 프로젝트의 요구사항 및 성능을 고려해서 적절한 방식을 선택해야 합니다.
2-1. Offset-based Pagination (오프셋 기반 페이지네이션)
장점: 상대적으로 간편한 구현, 페이지 지향적 사용
- limit, offset을 사용하여 상대적으로 간편하게 구현할 수 있습니다.
- 특정 페이지를 가져오기에 용이하고, 특정 페이지로 쉽게 이동할 수 있습니다.
단점: 데이터 중복, 서버 부하
- 데이터가 중간에 추가되거나 삭제될 경우, 페이지가 겹칠 수 있어 일관된 데이터 표현이 어려울 수 있습니다.
- 큰 데이터셋 사용 시 서버는 매 요청마다 데이터셋의 처음부터 offset을 계산하고, 해당 범위의 데이터를 반환해야 하므로 부하가 증가합니다.
2-2. Cursor-based Pagination (커서 기반 페이지네이션)
장점: 데이터 일관성, 서버 부하 감소
- 특정 커서 위치부터 다음 데이터를 요청하기 때문에 중복된 데이터 없이 일관된 정보를 받을 수 있습니다.
- 사용자가 현재까지 로드한 마지막 데이터의 위치를 기반으로 다음 데이터를 가져오기 때문에 서버 부하가 줄어듭니다.
단점: 구현 복잡성, 페이지 지향적 사용의 어려움
- 다음 데이터를 가져오려면 특정 커서 위치가 필요하기 때문에 일부 상황에서는 오프셋 기반보다 구현이 더 복잡할 수 있습니다.
- 페이지 네비게이션 및 특정 페이지로 이동이 필요한 경우, 별도의 로직 구현이 필요해서 상대적으로 더 복잡할 수 있습니다.
3. Cursor-based Pagination 구현하기
Cursor-based Pagination
1. 초기 데이터 로드: 처음에는 cursor 빈 값으로 전달, postRead 메서드 호출
2. 다음 페이지 처리: 응답 데이터의 next_cursor 값 cursor 매개변수로 전달, 다음 페이지의 데이터 가져오기
3. 데이터 소스 업데이트: 새롭게 받아온 새 게시물 데이터 postDataList에 추가
4. 새 게시글 표시를 위한 TableView Reload
3-1.
게시글이 새롭게 추가되거나 수정/삭제된 경우, 실시간으로 반영하기 위해 viewWillAppear 메서드에 구현함
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.isHidden = false
postDataList.data.removeAll()
postRead(cursor: "") // 1. 초기 데이터 로드
}
3-2.
마지막에 표시되는 셀의 인덱스가 지정한 범위 내에 있는지 확인하고,
커서 값과 함께 postRead 메서드를 사용해 현재 콘텐츠의 끝에 도달하기 전에 데이터를 사전에 로드함
private func prefetchData(for indexPaths: [IndexPath]) {
guard let lastIndexPath = indexPaths.last else {
return
}
let lastIndex = lastIndexPath.row
let totalCount = postDataList.data.count
if lastIndex >= totalCount - 3 {
// 2. 다음 페이지 처리
postRead(cursor: postDataList.next_cursor)
}
}
3-3.
UITableViewDataSourcePrefetching 프로토콜의 메서드를 활용해 데이터를 미리 예측하고 로드하여 성능 최적화
extension HomeViewController: UITableViewDataSourcePrefetching {
// 테이블 뷰에서 특정 행이 곧 표시될 것으로 예상할 때 호출됨
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths {
print("prefetchRowsAt: \(indexPath.row)")
}
prefetchData(for: indexPaths)
}
// 테이블 뷰에서 프리패칭된 특정 행이 더이상 표시되지 않을 것이라고 결정될 때 호출됨
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths {
print("cancelPrefetchingForRowsAt \(indexPath.row)")
}
}
}
3-4.
불필요한 오류를 방지하기 위해 guard 문을 활용해 예외 처리 구현함
private func postRead(cursor: String) {
print(#function)
// 더이상 로드할 데이터 없을 시 예외처리
guard cursor != "0" else {
print("더이상 로드 할 데이터가 없습니다.")
return
}
// 게시글 조회 API
PostAPIManager.shared.postRead(next: cursor)
.subscribe(with: self) { owner, result in
switch result {
case .success(let data):
print("포스트 조회 성공")
// 3. 데이터 소스 업데이트
owner.postDataList.data.append(contentsOf: data.data)
owner.postDataList.next_cursor = data.next_cursor
// 4. 테이블뷰 리로드
owner.mainView.tableView.reloadData()
case .failure(let error):
print("포스트 조회 실패: ", error)
owner.showAlertMessage(title: "Error", message: "게시글 조회에 실패했어요. 😢\n다시 시도해 주세요.")
}
}
.disposed(by: disposeBag)
}
참고한 블로그
Cursor based Pagination(커서 기반 페이지네이션)이란? - Querydsl로 무한스크롤 구현하기
Cursor based Pagination, 커서 기반 페이징, 무한스크롤
velog.io
'iOS > Trouble Shooting' 카테고리의 다른 글
| NotificationCenter를 통한 값 전달 시 주의해야 할 점! (2) | 2023.11.14 |
|---|