Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do Diffable Datasources treat class and struct types differently?

Diffable datasources require specifying a SectionIdentifierType and an ItemIdentifierType and these types have to conform to Hashable

Supposedly they must conform to Hashable so that the datasource can do its diffing.

So why does it behave differently depending on if the identifier type is a class or a struct even when the == and hash functions are the same? Or even the === function is overridden for classes so that it acts more like a value type?

Example:

import UIKit

public class DebugViewController: UIViewController {

    typealias SectionType = IntWrapper
    typealias ItemType = IntWrapper
    
    public class IntWrapper: Hashable {
        public static func == (lhs: DebugViewController.IntWrapper, rhs: DebugViewController.IntWrapper) -> Bool {
            lhs.number == rhs.number
        }
        public static func === (lhs: DebugViewController.IntWrapper, rhs: DebugViewController.IntWrapper) -> Bool {
            lhs.number == rhs.number
        }
        public func hash(into hasher: inout Hasher) {
            hasher.combine(number)
        }
        var number: Int
        
        init(number: Int) {
            self.number = number
        }
    }
    
    private var dataSource: UITableViewDiffableDataSource<SectionType, ItemType>!
    
    @IBOutlet var tableView: UITableView!
    
    public override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "DefaultCell")
        
        dataSource = UITableViewDiffableDataSource<SectionType, ItemType>(tableView: tableView) { (tableView, indexPath, item) -> UITableViewCell? in
            let cell = tableView.dequeueReusableCell(withIdentifier: "DefaultCell")!
            cell.textLabel?.text = "\(item.number)"
            return cell
        }
        
        apply()
    }
    
    @IBAction func buttonTapped(_ sender: Any) {
        apply()
    }
    
    func apply() {
        var snapshot = NSDiffableDataSourceSnapshot<SectionType, ItemType>()
        
        let sections = [IntWrapper(number: 0)]
        let items = [IntWrapper(number: 1)]
        snapshot.appendSections(sections)
        sections.forEach { snapshot.appendItems( items, toSection: $0) }
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

If IntWrapper is a struct the table view does nothing when apply() is called (apply() essentially loads in the same data) For me, this is the expected behavior.

If IntWrapper is a class the table view reloads when apply() is called. Also, the hash() and == functions are NOT even called.

I don't think this can be answered unless someone has access to the source (hint, hint) or unless I made some mistake in my example.

like image 817
Bryan Bryce Avatar asked Apr 29 '21 20:04

Bryan Bryce


People also ask

How does Diffable data source work?

Overview. A diffable data source object is a specialized type of data source that works together with your table view object. It provides the behavior you need to manage updates to your table view's data and UI in a simple, efficient way.

How do you update a Diffable data source?

The only mechanism to update the data for existing items is to apply a new snapshot containing the new data structures, which causes the diffable data source to perform a delete and an insert for each changed item.


1 Answers

After some investigation I found that UITableViewDiffableDataSource uses NSOrderedSet under the hood. Before passing the array of identifiers to the ordered set it is being converted to an array of Objective-C objects (by means of Swift._bridgeAnythingToObjectiveC<τ_0_0>(τ_0_0) -> Swift.AnyObject function). Because Swift and Objective-C classes share same memory layout they are passed as is. NSOrderedSet then relies on the hash and isEqual: Objective-C methods instead of Hashable, and Swift provides default implementations for those same as for NSObject even when a class is not subclassed from NSObject, but there's no forwarding calls to Hashable (only the other way round).

That said, the only correct way of using classes in diffable data sources is to subclass them from NSObject or at least implement hash() and isEqual(_:) methods with @objc annotation.

like image 115
Nickolay Tarbayev Avatar answered Nov 15 '22 07:11

Nickolay Tarbayev