由 Ryo Aoyama 和 贡献者们 用 ❤️ 制作
💡 最快的 O(n) 复杂度差异算法,针对 Swift 集合进行了优化
💡 计算差异,用于 UIKit
、AppKit
和 Texture 中列表 UI 的批量更新
💡 支持线性和分段集合,即使包含重复项
💡 支持 所有类型的差异,用于动画 UI 批量更新
这是一个为 Carbon 开发的差异算法,可独立工作。
该算法基于 Paul Heckel 算法进行了优化。
另请参阅他在 1978 年发布的论文 "一种隔离文件之间差异的技术"。
它允许在 O(n) 的线性时间内计算所有类型的差异。
RxDataSources 和 IGListKit 也基于他的算法实现。
但是,在 UITableView
、UICollectionView
等的 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
的相等性来了解用户是否已更新。
对于符合 Equatable
或 Hashable
的类型,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
执行 UITableView
和 UICollectionView
的差异批量更新。
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.1
和 Swift 5.1
编译的代码,并使用 -O
优化,在 iPhone11 Pro simulator
上运行来测量的。
使用 Foundation.UUID
作为集合的元素。
时间(秒) | |
---|---|
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 |
时间(秒) | |
---|---|
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) |
线性 | 分段 | 重复元素/节 | |
---|---|---|---|
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 | ❌ | ❌ | ❌ | ❌ |
要仅使用算法而不使用 UI 扩展,请将以下内容添加到您的 Podfile
中
pod 'DifferenceKit/Core'
要将 DifferenceKit 与 UIKit 扩展一起使用,请将以下内容添加到您的 Podfile
中
pod 'DifferenceKit'
或
pod 'DifferenceKit/UIKitExtension'
要将 DifferenceKit 与 AppKit 扩展一起使用,请将以下内容添加到您的 Podfile
中
pod 'DifferenceKit/AppKitExtension'
watchOS 没有 UI 扩展。
要仅使用算法而不使用 UI 扩展,请将以下内容添加到您的 Podfile
中
pod 'DifferenceKit/Core'
将以下内容添加到您的 Cartfile
中
github "ra1028/DifferenceKit"
选择 Xcode 菜单 File > Swift Packages > Add Package Dependency
并使用 GUI 输入存储库 URL。
Repository: https://github.com/ra1028/DifferenceKit
将以下内容添加到您的 Package.swift
的依赖项中
.package(url: "https://github.com/ra1028/DifferenceKit.git", from: "version")
欢迎提出 pull request、错误报告和功能请求 🚀
请参阅 CONTRIBUTING 文件,了解如何为 DifferenceKit 做出贡献。
DifferenceKit 是参考以下优秀材料和框架开发的。
使用此库的优秀 OSS 列表。它们还有助于理解如何使用 DifferenceKit。
我尊重并 ❤️ 所有涉及差异计算的库。
DifferenceKit 在 Apache 2.0 许可证下发布。