PHDiff
是一个非常高效的 diff 算法,采用 Swift 实现,具有线性时间的空间和时间的复杂度。
基于 Paul Heckel 的论文: 关于隔离文件之间差异的技术。
给定两个不同的数组 A 和 B,数组 A 要进行哪些步骤可以变成 B?
PHDiff 可以通过计算所需的插入、删除、移动和更新来回答这个问题!
PHDiff 还可以提供 UITableView 和 UICollectionView 更改的批量更新,因为它非常快,所以可以直接在主队列上使用。
要求
- iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+
- Xcode 11.0+
- Swift 5.0+
安装
CocoaPods
要使用 CocoaPods 将 PHDiff 整合到您的 Xcode 项目中,请指定您的 Podfile
文件。
use_frameworks!
target '<Your Target Name>' do
pod 'PHDiff', '~> 1.2'
end
Carthage
要将PHDiff集成到您的Xcode项目中使用Carthage,请在您的Cartfile
中指定它。
github "andre-alves/PHDiff" ~> 1.2
运行carthage update
来构建框架,并将构建好的PHDiff.framework
拖入您的Xcode项目。
Swift Package Manager
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的文件中导入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)
}
强烈的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"]
强烈的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()
}
}
注意
- 两种方法都是线性和空间复杂度。
- 请
不要
将steps(fromArray:toArray:)的结果应用于'fromArray'。结果没有按预期排序,期望由于每个步骤引起的索引偏移量改变。如果需要此类结果,请使用sortedSteps
。
为了diff模型,它们需要符合Diffable协议
可比较协议
Diffable 在扩展 Equatable 协议的过程中提供了一种 diffIdentifier。它可以是一个字符串、整数,或者任何符合 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 获取详细信息。