はじめに
こんにちは、ローソンデジタルイノベーション(LDI)でiOSエンジニアを担当している山形です。
先日、以下の記事でDiffableDataSourceへの移行についてご紹介しました。
techblog.ldi.co.jp
今回はDiffableDataSourceへの移行にあたって必要なSectionIdentifierTypeとItemIdentifierTypeに該当するデータをHashableに準拠させる上で注意したいことをご紹介します。
そのため一通りDiffableDataSourceへの対応は済んでいる前提でのお話になります。
当記事でDiffableDataSourceと記載しているものは以下の2つを対象としております。
掲載しているサンプルコードはUICollectionViewDiffableDataSourceを利用する例となっていますが、どちらの場合でもお役に立てるかと思います。
developer.apple.com
developer.apple.com
SectionIdentifierTypeとItemIdentifierTypeをHashableに準拠させる上での注意点
DiffableDataSourceにおいて Hashable プロトコルは結構重要で、Hashableに準拠することで、DiffableDataSource はSectionIdentifierType と ItemIdentifierType を使用して、セクションとアイテムを一意に識別します。
ではHashableに準拠させる上での注意点をご紹介していきます。
【注意点】Hashable実装の落とし穴:表示内容の変更が反映されないケース
例えばセルにタイトルと説明文を表示させるためのHashableなオブジェクトを用意したとしましょう。
以下のような構造体で良さそうです。
struct LDITechBlogItem: Hashable { private let id: Int // Hash衝突を避けるため一意のIDも持っておきます。冒頭で紹介した記事に理由を記載しています。 let title: String let description: String }
例えば、この構造体を取り扱うセルをユーザによる操作など何らかの契機に内容はそのままに見た目を切り替えたいとします。
当記事で説明するにあたってセルの生成とセットアップを以下のようにしてみました。
以下のようなセルのタイプを定義しました。
enum LDITechBlogCellType { case normal case Special }
セルの生成とセットアップを以下のように行って表示内容に反映させてみます。
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "LDITechBlogCellReuseIdentifier", for: indexPath) as? LDITechBlogCell else { return UICollectionViewCell() } cell.setup(item: ldiTechBlogItem, cellType: cellType) // この部分で`LDITechBlogItem`と`LDITechBlogCellType`を別々で渡しています。
DiffableDataSourceに対応するための対応を一通り済ませいざ、動かしてみるとLDITechBlogCellTypeを切り替えた時にセルが更新されません!
理由の解説と解決策
理由
なぜ更新されないのかについて解説します。
理由は単純でHashableオブジェクトであるLDITechBlogCellItemにLDITechBlogCellTypeがプロパティとして定義されていないためです。
前述の通りDiffableDataSourceがセクションとアイテムを一意に識別しているのは Hashableに準拠したSectionIdentifierType と ItemIdentifierType なのです。
DiffableDataSourceは、apply(_:animatingDifferences:)メソッドが呼ばれた時に、以前のスナップショットと新しいスナップショットを比較します。
この比較は、SectionIdentifierType と ItemIdentifierType の Hashable に基づいて行われます。LDITechBlogItem に cellType が含まれていない場合、title と description が同じであれば、DiffableDataSource はアイテムが変更されていないと判断し、セルの更新を行いません。
解決策
解決策としてはLDITechBlogCellTypeに差分更新に必要な情報を持たせ適切にハッシュ値が更新できるようにすることです。
struct LDITechBlogItem: Hashable {
private let id: Int
let title: String
let description: String
+ let cellType: LDITechBlogCellType
}
このようにすることでLDITechBlogCellTypeを切り替えた時に適切にハッシュ値が更新され表示も更新できるようになります。
最後に
今回はDiffableDataSource対応におけるHashableに準拠する上で注意したいことを記事にしました。
Hashableオブジェクトに差分更新に必要な情報をしっかり持たせるようにすることの重要性についてはご理解いただけたでしょうか。
逆にHashableオブジェクトには差分更新に適用させたくない情報をあえて持たせないことや、Hashableに準拠する上でhash(into:)メソッドを自前で実装してハッシュ値に含めないなど要件によっては実装の幅も広がりそうですね。
developer.apple.com
ここまで記事を読んでいただきありがとうございます!
今後もローソンデジタルイノベーションでは技術ブログを更新していきますので、是非「読者になる」で応援していただけますと幸いです。