PHDiff 1.2.0

PHDiff 1.2.0

测试已测试
Lang语言 SwiftSwift
许可证 MIT
Released最新版本2020年6月
SPM支持 SPM

Andre Alves 维护。



PHDiff 1.2.0

  • André Alves

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.swiftdependencies值一样简单。

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 的随机生成的数组

Performance Test

在 MacBook Pro (Retina, 13-inch, Mid 2014) - 2.6 GHz Intel Core i5 上进行测试。

感谢

Dwifft - 我使用了相同的枚举名称 'DiffStep'。

IGListKit - 我使用了协议 Diffable 的概念以及一个小优化来避免不必要的步骤。

许可证

PHDiff 在 MIT 许可证下发布。请参阅 LICENSE 获取详细信息。