Replace threadsafe var with new concurrency model (Actor?)

Up to you of course. I found SwiftUI code about 3 to 4 times shorter than the equivalent AppKit / UIKit code, and for me shorter / easier / more understandable code helps especially if the app is complex.

No, that's just the particular example code you found. Section or Item can be anything of your choice so long they are Hashable. e.g. a String. Or a struct. In the following partial example I am using a struct for Section:

Partial Example
struct Row: Hashable {
    let string: String
}

struct Section: Hashable {
    let header: String
    let rows: [Row]
}

typealias Snapshot = NSDiffableDataSourceSnapshot<Section, Row>
typealias Datasource = NSCollectionViewDiffableDataSource<Section, Row>

class Model {
    var sections: [Section] = []
    
    func createSnapshot() -> Snapshot {
        var snapshot = Snapshot()
        snapshot.appendSections(sections)
        sections.forEach { section in
            snapshot.appendItems(section.rows, toSection: section)
        }
    }
    
    private var calledOnMainQueue: Bool?
    
    func applySnapshot(dataSource: Datasource, snapshot: Snapshot) {
        // can be on background or main queue, but be consistent
        if calledOnMainQueue == nil {
            calledOnMainQueue = Thread.isMainThread
        }
        dispatchPrecondition(condition: calledOnMainQueue! ? .onQueue(.main) : .notOnQueue(.main))
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

Whether section contains items themselves or just the key using which you can obtain the items for the section is up to you. Ditto for the Item (row) - can be the "whole thing" or just the key.

You may find this full example applicable. It is for iOS but there's virtually no difference (just a change from UIDiffableDataSource to NSDiffableDataSource and UITableView / UICollectionView to NSTableView / NSCollectionView.