はじめに
iOSでTableviewやCollectionViewを扱っていると、UIとデータとの間で不整合が起きた際に NSInternalInconsistencyException
というエラーを吐いてアプリが落ちるというのはよくある話だと思います。
TableViewに関してはiOS13から UITableViewDiffableDataSource が追加され、Apple曰くこの問題を回避できるらしいので、DELISH KITCHENのiOSアプリで採用してみました。
導入方法
Hashable化
セクションやアイテムに対応するオブジェクトがHashableに適合している必要があります。 今回は対象となるオブジェクトがユニークなIDを既に持っていたので簡単でした。
/// TableViewの各セルに対応するオブジェクト struct Item: Hashable { let id: Int ... /// 追加1 static func == (lhs: MessageDetailRowItem, rhs: MessageDetailRowItem) -> Bool { return lhs.id == rhs.message.id } /// 追加2 func hash(into hasher: inout Hasher) { hasher.combine(id) } }
UITableViewDataSourceをUITableViewDiffableDataSourceに変更する
struct Section { let items: [Item] }
このようなセクションがあると仮定して
class SomeClass: UITableViewDataSource { let sections: [Section] func numberOfSections(in tableView: UITableView) -> Int { return sections.count } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return sections[section].items.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { /// cellをデキューして加工して返す処理 } }
というUITableViewDataSourceの実装があった場合の変更点を示します。
まず、Section
をHashableに適合させます。
次に、UITableViewDataSourceの代わりにUITableViewDiffableDataSourceを使うように変更します。
class SomeClass { private var dataSource: UITableViewDiffableDataSource<Section, Item>? func setupDataSource(tableView: UITableView) { dataSource = UITableViewDiffableDataSource<Section, Item>(tableView: tableView, cellProvider: { [weak self] (tableView, indexPath, item) -> UITableViewCell? in /// func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCellと一緒の内容 } } func setupSnapshot() { var snapShot = NSDiffableDataSourceSnapshot<Section, Item>() let sections: [Section] = /// 省略 snapShot.appendSections(sections) sections.forEach { snapShot.appendItems($0.items, toSection: $0) } dataSource.apply(snapShot) } }
setupDataSource
はViewControllerのViewDidLoad、setupSnapshot
はsetupDataSource
より後でデータが取得できたタイミングで実行すれば良いと思います。
また、変更がある場合は現在のスナップショットをdataSource
から取得できるので、それに対して変更を加えて再度applyするだけで良いです。
performBatchUpdateなどの処理
dataSourceにapplyしたらTableViewにも反映されるので不要になります。
結果
日に数件エラーが出ていたのですが、0件になりました。
今回はTableViewに対しての改善でしたがCollectionViewにも同様のAPIが存在するので、そちらも改善していきたいと考えています。
参考
Appleが提供しているサンプルプロジェクトです。
2021年3月18日時点では WiFiSettingsViewController
や TableViewEditingViewController
がUITableViewDiffableDataSourceを使っているので参考になると思います!