是一个非常高效的 diff 算法,用 Swift 实现,在时间和空间上都具有线性复杂度。
基于 Paul Heckel 的论文:一种隔离文件差异的技术。
给定两个不同的数组 A 和 B,A 需要进行哪些步骤才能变成 B?
PHDiff 可以通过计算所需的插入、删除、移动和更新来回答这个问题!
PHDiff 还可以为 UITableView 和 UICollectionView 的更改提供批量更新,由于它速度极快,因此可以直接在主队列中使用。
要使用 CocoaPods 将 PHDiff 集成到您的 Xcode 项目中,请在您的 Podfile
中指定它
use_frameworks!
target '<Your Target Name>' do
pod 'PHDiff', '~> 1.2'
end
要使用 Carthage 将 PHDiff 集成到您的 Xcode 项目中,请在您的 Cartfile
中指定它
github "andre-alves/PHDiff" ~> 1.2
运行 carthage update
来构建框架,并将构建的 PHDiff.framework
拖到您的 Xcode 项目中。
Swift Package Manager 是一种用于自动化 Swift 代码分发的工具,并已集成到 swift
编译器中。
一旦您设置好 Swift 包,添加 PHDiff 作为依赖项就像将其添加到 Package.swift
的 dependencies
值一样简单。
dependencies: [
.package(url: "https://github.com/andre-alves/PHDiff.git", .upToNextMajor(from: "1.2.0"))
]
只需将 Sources 文件夹中的 .swift 文件复制到您的项目中。
根据安装方法,您可能需要在使用的文件中导入 PHDiff
import PHDiff
PHDiff 提供了两个方法:PHDiff.sortedSteps(fromArray:toArray:) 和 PHDiff.steps(fromArray:toArray:),它们都返回一个 DiffSteps 数组
public enum DiffStep<T> {
case insert(value: T, index: Int)
case delete(value: T, index: Int)
case move(value: T, fromIndex: Int, toIndex: Int)
case update(value: T, index: Int)
}
PHDiff.sortedSteps(fromArray:toArray:) 以排序的方式计算插入、删除和更新,这些操作可以应用于第一个数组以将其转换为第二个数组。
let a = ["a", "b", "c", "d"]
let b = ["e", "a", "d"]
let steps = PHDiff.sortedSteps(fromArray: a, toArray: b)
print(steps)
//[Delete c at index: 2, Delete b at index: 1, Insert e at index: 0]
print(a.apply(steps: steps))
//["e", "a", "d"]
PHDiff.steps(fromArray:toArray:) 计算插入、删除、移动和更新,仅用于批量操作(例如:UITableView 和 UICollectionView 批量更新)。
private func updateTableView(newColors: [DemoColor]) {
let steps = PHDiff.steps(fromArray: self.colors, toArray: newColors)
if steps.count > 0 {
tableView.beginUpdates()
self.colors = newColors // update your model here
var insertions: [IndexPath] = []
var deletions: [IndexPath] = []
var reloads: [IndexPath] = []
steps.forEach { step in
switch step {
case let .insert(_, index):
insertions.append(IndexPath(row: index, section: 0))
case let .delete(_, index):
deletions.append(IndexPath(row: index, section: 0))
case let .move(_, fromIndex, toIndex):
deletions.append(IndexPath(row: fromIndex, section: 0))
insertions.append(IndexPath(row: toIndex, section: 0))
case let .update(_, index):
reloads.append(IndexPath(row: index, section: 0))
}
}
tableView.insertRows(at: insertions, with: .automatic)
tableView.deleteRows(at: deletions, with: .automatic)
tableView.reloadRows(at: reloads, with: .automatic)
tableView.endUpdates()
}
}
重要提示
为了对您的模型进行差异比较,它们需要符合 Diffable 协议
Diffable 通过提供一个 diffIdentifier 扩展了 Equatable 协议。它可以是 String、Int 或任何符合 Hashable 协议的内容。您可以将其视为表示您的对象的唯一键。
struct DemoColor: Diffable {
let name: String
let r: Float
let g: Float
let b: Float
var diffIdentifier: String {
return name
}
}
func ==(lhs: DemoColor, rhs: DemoColor) -> Bool {
return lhs.name == rhs.name && lhs.r == rhs.r && lhs.b == rhs.b && lhs.g == rhs.g
}
注意:如果您的模型符合 Hashable,则不需要实现 diffIdentifier。
差异比较两个长度均为 1000 的随机生成的数组
在 MacBook Pro (Retina, 13-inch, Mid 2014) - 2.6 GHz Intel Core i5 上测试。
Dwifft - 我使用了相同的枚举名称 'DiffStep'。
IGListKit - 我使用了协议 Diffable 的概念和一个小的优化来避免不必要的步骤。
PHDiff 根据 MIT 许可证发布。 有关详细信息,请参见 LICENSE。