はじめに
こんにちは、ローソンデジタルイノベーション(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
ここまで記事を読んでいただきありがとうございます!
今後もローソンデジタルイノベーションでは技術ブログを更新していきますので、是非「読者になる」で応援していただけますと幸いです。