❌ 문제 발생....
Diffable data source detected an attempt to insert or append 5 item identifiers that
already exist in the snapshot. The existing item identifiers will be moved into place
instead, but this operation will be more expensive. For best performance, inserted item
identifiers should always be unique.
Set a symbolic breakpoint on BUG_IN_CLIENT_OF_DIFFABLE_DATA_SOURCE__IDENTIFIER_ALREADY_
EXISTS to catch this in the debugger. Item identifiers that already exist: ( )
UICollectionViewDiffableDataSource에서 중복된 item identifier가 추가되었기 때문에 발생한 오류입니다.
즉, 기존에 존재하는 item identifier(즉, Hashable한 값)가 다시 추가되면서, Diffable Data Source가 이를 중복으로 인식하여 데이터 누락이 발생할 수 있다는 의미입니다. 🚨
❌ 문제 재현....
지금 컬렉션뷰를 createCompositonalLayout() + UICollectionViewDiffableDataSource 로 구성했습니다.
컬렉션뷰는 총 4개의 섹션(airingToday, onTheAir, popular, topRated)으로 구성되어있습니다.
지금의 문제는 중복 데이터 발생으로 인해 섹션별로 데이터 누락이 발생합니다.
🔷 API 호출로 데이터를 받아 드릴 때는 전체 데이터다 올바르게 들어오는 것은 확인했습니다.
🔍 문제 원인
📌 (1) 데이터 모델 구조 문제
❌ 잘못된 점:
- TvTMDBResult(각 TV 프로그램 데이터)를 Diffable Data Source의 아이템(identifier)으로 직접 사용하면, id가 같은 데이터가 중복될 경우 문제가 발생함.
- 같은 id를 가진 TvTMDBResult가 여러 섹션에서 나타나도, Diffable Data Source는 이를 중복된 데이터로 간주하여 하나만 유지하려고 함.
- 결과적으로, 컬렉션 뷰에서 어떤 섹션에서는 데이터가 누락되는 문제가 발생함.
💡 잘못된 구조
- TvTMDBData -> TvTMDBResult 구조체 경우는 API를 통해 받아오는 데이터 모델 양식
- TvCombineData -> TvTMDBData의 섹션별 데이터를 통합 관리하는 구조체
// MARK: - TVCombineData
struct TvCombineData: Codable, Hashable {
var combineTMDB: [TvTMDBData]
}
// MARK: - TVTMDBData
struct TvTMDBData: Codable, Hashable {
var page: Int
var results: [TvTMDBResult]
var totalPages, totalResults: Int
var type: TvSectionType?
}
// MARK: - Result
struct TvTMDBResult: Codable, Hashable {
let adult: Bool
let id: Int
}
enum TvSectionType: String, Codable {
case airingToday = "Air Today"
case onTheAir = "On The Air"
case popular = "Popular"
case topRated = "TopRated"
}
➡️ dataSource를 선언할 때 제네릭 타입에 <섹션, 아이템> 인데, 여기서 아이템에 TvTMDBResult를 넣었습니다.
왜냐하면 API를 통해 받아온 프로그램의 데이터를 가지고 있다고 생각했기 때문입니다.
❌ TvTMDBResult를 직접 사용하면 문제 발생:
- 같은 id: 1001(예: "Breaking Bad")이 "Airing Today"와 "Popular"에 포함되어 있다면, Diffable Data Source가 중복된 것으로 판단하고 하나만 남기게 됨.
- 결과적으로 한 섹션에서 사라지는 문제가 발생.
private var dataSource: UICollectionViewDiffableDataSource<TvTMDBData, TvTMDBResult>?
🔹 Diffable Data Source 사용 (잘못된 경우)
private func reloadData() {
var snapshot = NSDiffableDataSourceSnapshot<TvTMDBData, TvTMDBResult>()
snapshot.appendSections([airingTodayData, popularData])
// ✅ airingToday 섹션 추가
snapshot.appendItems(airingTodayData.results, toSection: airingTodayData)
// ✅ popular 섹션 추가
snapshot.appendItems(popularData.results, toSection: popularData)
dataSource?.apply(snapshot, animatingDifferences: true)
}
❌ 문제점
- "Breaking Bad"(id: 1001)가 두 섹션(airingToday, popular)에 포함됨.
- Diffable Data Source는 id: 1001이 두 번 추가되었다고 판단하여 하나만 남김.
- 결과적으로 "Breaking Bad"가 한 섹션에서만 보이고 다른 섹션에서 사라짐. ❌
📌 (2) 컬렉션 뷰 설정 문제
❌ 잘못된 점:
- Diffable Data Source는 각 아이템이 고유한 식별자(Unique Identifier)를 가져야 하는데,
기존 구조에서는 같은 id를 가진 데이터가 여러 섹션에 들어갈 경우, 이를 같은 객체로 인식함. - 결과적으로 UICollectionViewDiffableDataSource에서 hash(into:)와 == 비교 연산을 통해 같은 객체로 판단되는 경우 중복 데이터가 제거됨.
- 중복된 item identifier가 추가되었다는 오류가 발생하면서, 일부 데이터가 컬렉션 뷰에서 표시되지 않음.
💡 오류 메시지
- 이미 존재하는 id를 가진 데이터가 추가되면서 중복 문제 발생! 🚨
Diffable data source detected an attempt to insert or append 5 item identifiers that already exist in the snapshot.
🔨 해결 방법
(1) TvResultItem을 추가하여 중복을 방지
✅ 변경점:
- TvTMDBResult를 그대로 사용하지 않고, 별도의 구조체 TvResultItem을 생성하여 sectionType을 함께 저장함으로써,
같은 id를 가지더라도 섹션이 다르면 다른 객체로 인식하도록 변경.
💡 변경 후 구조
struct TvResultItem: Hashable {
let tvResult: TvTMDBResult
let sectionType: TvSectionType
func hash(into hasher: inout Hasher) {
hasher.combine(tvResult.id)
hasher.combine(sectionType) // ✅ 섹션 정보도 함께 해싱하여 고유한 객체로 만듦
}
static func == (lhs: TvResultItem, rhs: TvResultItem) -> Bool {
return lhs.tvResult.id == rhs.tvResult.id &&
lhs.sectionType == rhs.sectionType // ✅ 같은 id라도 섹션이 다르면 다른 객체로 취급
}
}
✅ 이제 같은 id라도 sectionType이 다르면 다른 객체로 인식됨!
- "Breaking Bad"(id: 1001)가 "Airing Today"와 "Popular" 두 개의 섹션에 포함되더라도,
TvResultItem이 sectionType을 포함하므로, Diffable Data Source가 중복으로 인식하지 않음.
(2) Diffable Data Source에서 TvResultItem을 사용
✅ 변경점:
- UICollectionViewDiffableDataSource<TvTMDBData, TvResultItem>으로 변경하여 각 아이템을 TvResultItem으로 관리하도록 수정.
💡 변경 후 코드
- 이제 TvResultItem을 사용하여, 같은 TV 프로그램이라도 섹션이 다르면 다른 객체로 인식됨.
- 중복 문제 없이 모든 섹션에서 정상적으로 표시됨. 🎯
private var dataSource: UICollectionViewDiffableDataSource<TvTMDBData, TvResultItem>?
(3) 데이터 변환 과정에서 TvResultItem으로 래핑
✅ 변경점:
- TvTMDBResult를 Diffable Data Source에 추가할 때, TvResultItem으로 변환하여 추가해야 함.
💡 변경 후 코드 (Diffable Data Source에 데이터 추가)
- 기존 TvTMDBResult를 그대로 사용하지 않고, 각 결과를 TvResultItem으로 변환한 후 snapshot에 추가.
- 이제 같은 id를 가진 TV 프로그램이라도 섹션이 다르면 다른 객체로 관리됨! 🚀
private func reloadData() {
var snapshot = NSDiffableDataSourceSnapshot<TvTMDBData, TvResultItem>()
snapshot.appendSections(tvCombineSection.combineTMDB)
for section in tvCombineSection.combineTMDB {
let items = section.results.map {
TvResultItem(tvResult: $0, sectionType: section.type ?? .popular) // ✅ 변환
}
snapshot.appendItems(items, toSection: section)
}
dataSource?.apply(snapshot, animatingDifferences: true)
}
'Project > MovieClip' 카테고리의 다른 글
🤔 영화 정보를 통합해서 관리할 필요가 있나? (0) | 2025.02.21 |
---|---|
✅ hash(into:) 메서드와 == 연산자의 역할 (0) | 2025.02.20 |
⭐️ 버튼의 애니메이션 효과 주기 (0) | 2025.02.18 |
🤔 배우의 출연작 조회 (영화, 티비) (1) | 2025.02.18 |
🔥 배우의 영어로된 소개글을 한글로 번역하기 (1) | 2025.02.15 |