본문 바로가기
이모저모/UIKit

다양한 셀을 그리는 DiffableDataSource 데이터 구성하기 #2

by ARpple 2023. 11. 10.

AnyModelStore의 필요성

  • 접근 문제 → 데이터 Collection 아이템 접근을 first로 한다. (순회 발생)
var musicRegistration: UICollectionView.CellRegistration<UICollectionViewListCell,Item>{
        UICollectionView.CellRegistration { cell, indexPath, itemIdentifier in
            guard itemIdentifier.itemType == .music,
            let data:MusicItem = self.musicItems.first(where: {$0.id == itemIdentifier.id}) else {return}
        }
    }
  • 업데이트 문제 → 데이터 Collection 아이템을 인덱스로 접근한다. (순회 발생)
💡 배열 값을 업데이트 하기 위해선 인덱스 접근을 해야하는 것이 가장 큰 문제이다
func toggleShortVideo(item:Item){
        guard var dataIdx = self.shortVideoItems.firstIndex(where:{item.id == $0.id}) else {return}
        shortVideoItems[dataIdx].isLike.toggle()
    }

AnyModelStore

 

Building High-Performance Lists and Collection Views | Apple Developer Documentation

Improve the performance of lists and collections in your app with prefetching and image preparation.

developer.apple.com

이 예제 코드를 이용함!! → 좀 더 단순화한 코드

import Foundation
import Combine
protocol ModelStoreAble {
    associatedtype Model: Identifiable
    func fetchByID(_ id: Model.ID) -> Model!
}

class AnyModelStore<Model: Identifiable>: ModelStoreAble {
    private var models = [Model.ID: Model]()

    init(_ models: [Model]) {
        // 모델의 배열을 [모델 아이디 : 모델] 형식의 Dictionary로 바꿔준다.
        // => 고유한 값에 고유한 데이터만 존재
        self.models = models.groupingByUniqueID()
    }

    // 모델의 id를 통해서 저장소에서 Model을 가져온다
    func fetchByID(_ id: Model.ID) -> Model! {
        return self.models[id]
    }
    func insertModel(item: Model){
        models[item.id] = item
    }
    func removeModel(_ id:Model.ID){
        models.removeValue(forKey: id)
    }
}
// 배열 익스텐션 -> 원소들이 Identifiable을 준수하는 것들에 적용되는 함수
extension Sequence where Element: Identifiable {
    func groupingByID() -> [Element.ID: [Element]] {
        return Dictionary(grouping: self, by: { $0.id })
    }

    func groupingByUniqueID() -> [Element.ID: Element] {
        return Dictionary(uniqueKeysWithValues: self.map { ($0.id, $0) })
    }
}

AnyModelStore를 이용해서 개선한 위 문제점

  • 접근 문제: O(1)의 시간 복잡도로 접근 가능
var musicRegistration: UICollectionView.CellRegistration<UICollectionViewListCell,Item>{
        UICollectionView.CellRegistration { cell, indexPath, itemIdentifier in
            guard itemIdentifier.itemType == .music,
                  let data: MusicItem = self.musicStore.fetchByID(itemIdentifier.id) else {return}
        }
    }
  • 업데이트 문제: O(1)의 시간 복잡도로 접근 가능
func toggleShortVideo(item:Item){
        guard var data = self.shortVideoStore.fetchByID(item.id) else {return}
        data.isLike.toggle()
        self.shortVideoStore.insertModel(item: data)
    }

전체 개선 코드

struct MusicItem:Identifiable,Hashable{
    var id = UUID()
    var artist = "Newjeans"
}
struct ShortVideo:Identifiable,Hashable{
    var id = UUID()
    var isLike = false
    var tictoker = "Leo"
}

struct VideoItem:Identifiable,Hashable{
    var id = UUID()
    var youtuber = "calmdownman"
}
final class TempVC: UIViewController{
    enum MediaType{
        case video
        case music
        case shortVideo
    }
    struct Item:Identifiable,Hashable{
        var id:UUID
        var itemType: MediaType
    }
    var videoStore:AnyModelStore<VideoItem>!
    var shortVideoStore:AnyModelStore<ShortVideo>!
    var musicStore: AnyModelStore<MusicItem>!
    var dataSource: UICollectionViewDiffableDataSource<MediaType,Item>!
    override func viewDidLoad() {
        super.viewDidLoad()
        var snapshot = NSDiffableDataSourceSnapshot<MediaType,Item>()
        var videoItems = (0...20).map{_ in VideoItem()}
        var shortVideoItems = (0...40).map{_ in ShortVideo()}
        var musicItems = (0...60).map{_ in MusicItem()}
        videoStore = .init(videoItems)
        shortVideoStore = .init(shortVideoItems)
        musicStore = .init(musicItems)
        snapshot.appendSections([.video,.music,.shortVideo])
        snapshot.appendItems(videoItems.map{Item(id: $0.id, itemType: .video)},toSection: .video)
        snapshot.appendItems(musicItems.map{Item(id: $0.id, itemType: .music)},toSection: .music)
        snapshot.appendItems(shortVideoItems.map{Item(id: $0.id, itemType: .music)},toSection: .shortVideo)
        dataSource.apply(snapshot,animatingDifferences: true)
    }
    var musicRegistration: UICollectionView.CellRegistration<UICollectionViewListCell,Item>{
        UICollectionView.CellRegistration { cell, indexPath, itemIdentifier in
            guard itemIdentifier.itemType == .music,
                  let data: MusicItem = self.musicStore.fetchByID(itemIdentifier.id) else {return}
        }
    }
    func toggleShortVideo(item:Item){
        guard var data = self.shortVideoStore.fetchByID(item.id) else {return}
        data.isLike.toggle()
        self.shortVideoStore.insertModel(item: data)
    }
}

댓글