一个快速且灵活的 O(n) 复杂度 Swift 集合差异算法框架。
该算法基于 Paul Heckel 算法进行了优化。

Swift5 Release CocoaPods Carthage Swift Package Manager
CI Status Platform Lincense


Ryo Aoyama贡献者们 用 ❤️ 制作


特性

💡 最快的 O(n) 复杂度差异算法,针对 Swift 集合进行了优化

💡 计算差异,用于 UIKitAppKitTexture 中列表 UI 的批量更新

💡 支持线性和分段集合,即使包含重复项

💡 支持 所有类型的差异,用于动画 UI 批量更新


算法

这是一个为 Carbon 开发的差异算法,可独立工作。
该算法基于 Paul Heckel 算法进行了优化。
另请参阅他在 1978 年发布的论文 "一种隔离文件之间差异的技术"
它允许在 O(n) 的线性时间内计算所有类型的差异。
RxDataSourcesIGListKit 也基于他的算法实现。

但是,在 UITableViewUICollectionView 等的 performBatchUpdates 中,存在同时应用时会导致崩溃的差异组合。
为了解决这个问题,DifferenceKit 采取了一种方法,将差异集拆分为最小的阶段,这些阶段可以执行批量更新而不会崩溃。

实现在此


开始使用

基本用法

要计算差异的元素的类型必须符合 Differentiable 协议。
differenceIdentifier 的类型是泛型关联类型

struct User: Differentiable {
    let id: Int
    let name: String

    var differenceIdentifier: Int {
        return id
    }

    func isContentEqual(to source: User) -> Bool {
        return name == source.name
    }
}

在上面的定义中,id 唯一标识元素,并通过比较源和目标中元素的 name 的相等性来了解用户是否已更新。

对于符合 EquatableHashable 的类型,Differentiable 具有默认实现:

// If `Self` conforming to `Hashable`.
var differenceIdentifier: Self {
    return self
}

// If `Self` conforming to `Equatable`.
func isContentEqual(to source: Self) -> Bool {
    return self == source
}

因此,您可以简单地

extension String: Differentiable {}

通过从符合 Differentiable 的元素的两个集合创建 StagedChangeset 来计算差异

let source = [
    User(id: 0, name: "Vincent"),
    User(id: 1, name: "Jules")
]
let target = [
    User(id: 1, name: "Jules"),
    User(id: 0, name: "Vincent"),
    User(id: 2, name: "Butch")
]

let changeset = StagedChangeset(source: source, target: target)

如果要在集合中包含符合 Differentiable 的多种类型,请使用 AnyDifferentiable

let source = [
    AnyDifferentiable("A"),
    AnyDifferentiable(User(id: 0, name: "Vincent"))
]

在分段集合的情况下,节本身必须具有唯一的标识符,并且能够比较是否存在更新。
因此,每个节都必须符合 DifferentiableSection 协议,但在大多数情况下,您可以使用符合该协议的通用类型 ArraySection
ArraySection 需要符合 Differentiable 的模型,以便与其他节进行区分

enum Model: Differentiable {
    case a, b, c
}

let source: [ArraySection<Model, String>] = [
    ArraySection(model: .a, elements: ["A", "B"]),
    ArraySection(model: .b, elements: ["C"])
]
let target: [ArraySection<Model, String>] = [
    ArraySection(model: .c, elements: ["D", "E"]),
    ArraySection(model: .a, elements: ["A"]),
    ArraySection(model: .b, elements: ["B", "C"])
]

let changeset = StagedChangeset(source: source, target: target)

您可以使用创建的 StagedChangeset 执行 UITableViewUICollectionView 的差异批量更新。

⚠️ 不要忘记 使用 setData 闭包中传递的数据同步更新数据源引用的数据。差异是分阶段应用的,如果未能这样做,肯定会造成崩溃

tableView.reload(using: changeset, with: .fade) { data in
    dataSource.data = data
}

使用过多的差异进行批量更新可能会对性能产生不利影响。
使用 interrupt 闭包返回 true 则会回退到 reloadData

collectionView.reload(using: changeset, interrupt: { $0.changeCount > 100 }) { data in
    dataSource.data = data
}

[查看更多用法]


与其他框架的比较

尽可能在性能和功能上与其他流行优秀的框架进行公平的比较。
决定框架的优劣。
我知道每个框架都有不同的优势。
下面是被比较的框架及其版本。

性能比较

基准测试项目在此
性能是通过使用 Xcode11.1Swift 5.1 编译的代码,并使用 -O 优化,在 iPhone11 Pro simulator 上运行来测量的。
使用 Foundation.UUID 作为集合的元素。

- 从 5,000 个元素到 1,000 个删除、1,000 个插入和 200 个洗牌

时间(秒)
DifferenceKit 0.0019
RxDataSources 0.0074
IGListKit 0.0346
FlexibleDiff 0.0161
DeepDiff 0.0373
Differ 1.0581
Dwifft 0.4732
Swift.CollectionDifference 0.0620

- 从 100,000 个元素到 10,000 个删除、10,000 个插入和 2,000 个洗牌

时间(秒)
DifferenceKit 0.0348
RxDataSources 0.1024
IGListKit 0.7002
FlexibleDiff 0.2189
DeepDiff 0.5537
Differ 153.8007
Dwifft 187.1341
Swift.CollectionDifference 5.0281

功能比较

- 算法

基础算法 复杂度
DifferenceKit Heckel O(N)
RxDataSources Heckel O(N)
FlexibleDiff Heckel O(N)
IGListKit Heckel O(N)
DeepDiff Heckel O(N)
Differ Myers O(ND)
Dwifft Myers O(ND)
Swift.CollectionDifference Myers O(ND)

* Heckel 算法
* Myers 算法

- 支持的集合

线性 分段 重复元素/节
DifferenceKit
RxDataSources
FlexibleDiff
IGListKit
DeepDiff
Differ
Dwifft
Swift.CollectionDifference

* 线性 表示一维集合
* 分段 表示二维集合

- 支持的元素差异

删除 插入 移动 重新加载 跨节移动
DifferenceKit
RxDataSources
FlexibleDiff
IGListKit
DeepDiff
Differ
Dwifft
Swift.CollectionDifference

- 支持的节差异

删除 插入 移动 重新加载
DifferenceKit
RxDataSources
FlexibleDiff
IGListKit
DeepDiff
Differ
Dwifft
Swift.CollectionDifference

要求


安装

CocoaPods

要仅使用算法而不使用 UI 扩展,请将以下内容添加到您的 Podfile

pod 'DifferenceKit/Core'

iOS / tvOS

要将 DifferenceKit 与 UIKit 扩展一起使用,请将以下内容添加到您的 Podfile

pod 'DifferenceKit'

pod 'DifferenceKit/UIKitExtension'

macOS

要将 DifferenceKit 与 AppKit 扩展一起使用,请将以下内容添加到您的 Podfile

pod 'DifferenceKit/AppKitExtension'

watchOS

watchOS 没有 UI 扩展。
要仅使用算法而不使用 UI 扩展,请将以下内容添加到您的 Podfile

pod 'DifferenceKit/Core'

Carthage

将以下内容添加到您的 Cartfile

github "ra1028/DifferenceKit"

适用于 Apple 平台的 Swift Package Manager

选择 Xcode 菜单 File > Swift Packages > Add Package Dependency 并使用 GUI 输入存储库 URL。

Repository: https://github.com/ra1028/DifferenceKit

Swift Package Manager

将以下内容添加到您的 Package.swift 的依赖项中

.package(url: "https://github.com/ra1028/DifferenceKit.git", from: "version")

贡献

欢迎提出 pull request、错误报告和功能请求 🚀
请参阅 CONTRIBUTING 文件,了解如何为 DifferenceKit 做出贡献。


致谢

参考书目

DifferenceKit 是参考以下优秀材料和框架开发的。

使用 DifferenceKit 的 OSS

使用此库的优秀 OSS 列表。它们还有助于理解如何使用 DifferenceKit。

其他差异库

我尊重并 ❤️ 所有涉及差异计算的库。


许可证

DifferenceKit 在 Apache 2.0 许可证下发布。