Skip to content

Commit

Permalink
Merge pull request #38 from ra1028/v0.7.0
Browse files Browse the repository at this point in the history
v0.7.0
  • Loading branch information
ra1028 authored Oct 2, 2018
2 parents d22741e + ff1cd54 commit 403a3ab
Show file tree
Hide file tree
Showing 30 changed files with 781 additions and 342 deletions.
2 changes: 1 addition & 1 deletion .swift-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.1
4.2
8 changes: 1 addition & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,5 @@ matrix:
- xcodebuild build-for-testing test-without-building -scheme DifferenceKit -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 8' ENABLE_TESTABILITY=YES | xcpretty - c
- xcodebuild build-for-testing test-without-building -scheme DifferenceKit -configuration Release -sdk appletvsimulator -destination 'platform=tvOS Simulator,name=Apple TV' ENABLE_TESTABILITY=YES | xcpretty -c
- xcodebuild build -scheme DifferenceKit -configuration Release -sdk watchsimulator -destination 'platform=watchOS Simulator,name=Apple Watch - 38mm' ENABLE_TESTABILITY=YES | xcpretty -c
- os: osx
language: objective-c
osx_image: xcode9.4
script:
- xcodebuild build -scheme DifferenceKit -configuration Release ENABLE_TESTABILITY=YES | xcpretty -c
- xcodebuild build -scheme DifferenceKit -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 8' | xcpretty -c
notifications:
email: false
email: false
2 changes: 1 addition & 1 deletion DifferenceKit.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'DifferenceKit'
spec.version = '0.6.0'
spec.version = '0.7.0'
spec.author = { 'ra1028' => 'r.fe51028.r@gmail.com' }
spec.homepage = 'https://github.com/ra1028/DifferenceKit'
spec.documentation_url = 'https://ra1028.github.io/DifferenceKit'
Expand Down
8 changes: 6 additions & 2 deletions DifferenceKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
6B2DF878210E2C12004D2D40 /* DifferenceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B2DF86E210E2C12004D2D40 /* DifferenceKit.framework */; };
6B444B392163312700AEE32B /* ContentEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B444B382163312700AEE32B /* ContentEquatable.swift */; };
6B5B409C211066BF00A931DB /* AlgorithmTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5B4093211066BF00A931DB /* AlgorithmTest.swift */; };
6B5B409D211066BF00A931DB /* ArraySectionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5B4094211066BF00A931DB /* ArraySectionTest.swift */; };
6B5B409E211066BF00A931DB /* StagedChangesetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5B4095211066BF00A931DB /* StagedChangesetTest.swift */; };
Expand Down Expand Up @@ -42,6 +43,7 @@
6B2DF86E210E2C12004D2D40 /* DifferenceKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DifferenceKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6B2DF877210E2C12004D2D40 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
6B2DF88A210E39A8004D2D40 /* DifferenceKit.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DifferenceKit.xcconfig; sourceTree = "<group>"; };
6B444B382163312700AEE32B /* ContentEquatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentEquatable.swift; sourceTree = "<group>"; };
6B5B4086211066B300A931DB /* Algorithm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Algorithm.swift; sourceTree = "<group>"; };
6B5B4088211066B300A931DB /* UIKitExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitExtension.swift; sourceTree = "<group>"; };
6B5B408A211066B300A931DB /* ArraySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArraySection.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -117,6 +119,7 @@
6B5B4086211066B300A931DB /* Algorithm.swift */,
6B5B408F211066B300A931DB /* Changeset.swift */,
6B5B408C211066B300A931DB /* StagedChangeset.swift */,
6B444B382163312700AEE32B /* ContentEquatable.swift */,
6B5B4091211066B300A931DB /* Differentiable.swift */,
6B5B408E211066B300A931DB /* DifferentiableSection.swift */,
6B5B408B211066B300A931DB /* AnyDifferentiable.swift */,
Expand Down Expand Up @@ -261,6 +264,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6B444B392163312700AEE32B /* ContentEquatable.swift in Sources */,
6B5B40A8211066EA00A931DB /* Changeset.swift in Sources */,
6B5B40AB211066EA00A931DB /* AnyDifferentiable.swift in Sources */,
755D649621514ECB0049A3C5 /* AppKitExtension.swift in Sources */,
Expand Down Expand Up @@ -358,7 +362,7 @@
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
Expand Down Expand Up @@ -415,7 +419,7 @@
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
Expand Down
24 changes: 11 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,14 @@ In the case of definition above, `id` uniquely identifies the element and get to

There are default implementations of `Differentiable` for the types that conformed to `Equatable` or `Hashable`
```swift
public extension Differentiable where Self: Equatable {
func isContentEqual(to source: Self) -> Bool {
return self == source
}
// If `Self` conform to `Hashable`.
var differenceIdentifier: Self {
return self
}

public extension Differentiable where Self: Hashable {
var differenceIdentifier: Self {
return self
}
// If `Self` conform to `Equatable`.
func isContentEqual(to source: Self) -> Bool {
return self == source
}
```
So, you can simply:
Expand Down Expand Up @@ -170,7 +168,7 @@ collectionView.reload(using: changeset, interrupt: { $0.changeCount > 100 }) { d

## Comparison with Other Frameworks
Made a fair comparison as much as possible in features and performance with other **popular** and **awesome** frameworks.
⚠️ This does `NOT` determine superiority or inferiority of the frameworks. I know that each framework has different benefits.
This does **NOT** determine superiority or inferiority of the frameworks. I know that each framework has different benefits.
The frameworks and its version that compared is below.

- [DifferenceKit](https://github.com/ra1028/DifferenceKit) - master
Expand Down Expand Up @@ -232,7 +230,7 @@ Use `Foundation.UUID` as an element.
#### - From 5,000 elements to 500 deleted and 500 inserted
| |Time(second)|
|:------------|:-----------|
|DifferenceKit|0.0032 |
|DifferenceKit|0.0022 |
|RxDataSources|0.0078 |
|FlexibleDiff |0.0168 |
|IGListKit |0.0412 |
Expand All @@ -244,7 +242,7 @@ Use `Foundation.UUID` as an element.
#### - From 10,000 elements to 1,000 deleted and 1,000 inserted
| |Time(second)|
|:------------|:-----------|
|DifferenceKit|0.0076 |
|DifferenceKit|0.0049 |
|RxDataSources|0.0143 |
|FlexibleDiff |0.0305 |
|IGListKit |0.0891 |
Expand All @@ -256,7 +254,7 @@ Use `Foundation.UUID` as an element.
#### - From 100,000 elements to 10,000 deleted and 10,000 inserted
| |Time(second)|
|:------------|:-----------|
|DifferenceKit|0.087 |
|DifferenceKit|0.057 |
|RxDataSources|0.179 |
|FlexibleDiff |0.356 |
|IGListKit |1.329 |
Expand All @@ -268,7 +266,7 @@ Use `Foundation.UUID` as an element.
---

## Requirements
- Swift4.1+
- Swift4.2+
- iOS 9.0+
- tvOS 9.0+
- OS X 10.9+
Expand Down
93 changes: 62 additions & 31 deletions Sources/Algorithm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public extension StagedChangeset where Collection: RangeReplaceableCollection, C
/// - target: A target collection to calculate differences.
///
/// - Complexity: O(n)
@inlinable
public init(source: Collection, target: Collection) {
self.init(source: source, target: target, section: 0)
}
Expand All @@ -42,6 +43,7 @@ public extension StagedChangeset where Collection: RangeReplaceableCollection, C
/// - section: An Int value to use as section index (or offset) of element.
///
/// - Complexity: O(n)
@inlinable
public init(source: Collection, target: Collection, section: Int) {
let sourceElements = ContiguousArray(source)
let targetElements = ContiguousArray(target)
Expand Down Expand Up @@ -134,6 +136,7 @@ public extension StagedChangeset where Collection: RangeReplaceableCollection, C
/// - target: A target sectioned collection to calculate differences.
///
/// - Complexity: O(n)
@inlinable
public init(source: Collection, target: Collection) {
typealias Section = Collection.Element
typealias SectionIdentifier = Collection.Element.DifferenceIdentifier
Expand Down Expand Up @@ -405,8 +408,9 @@ public extension StagedChangeset where Collection: RangeReplaceableCollection, C
}

/// The shared algorithm to calculate differences between two linear collections.
@inlinable
@discardableResult
private func differentiate<E: Differentiable, I>(
internal func differentiate<E: Differentiable, I>(
source: ContiguousArray<E>,
target: ContiguousArray<E>,
trackTargetIndexAsUpdated: Bool,
Expand Down Expand Up @@ -528,16 +532,23 @@ private func differentiate<E: Differentiable, I>(
}

/// A set of changes and metadata as a result of calculating differences in linear collection.
private struct DifferentiateResult<Index> {
typealias Metadata = (sourceTraces: ContiguousArray<Trace<Int>>, targetReferences: ContiguousArray<Int?>)

let deleted: [Index]
let inserted: [Index]
let updated: [Index]
let moved: [(source: Index, target: Index)]
let metadata: Metadata

init(
@usableFromInline
internal struct DifferentiateResult<Index> {
@usableFromInline
internal typealias Metadata = (sourceTraces: ContiguousArray<Trace<Int>>, targetReferences: ContiguousArray<Int?>)
@usableFromInline
internal let deleted: [Index]
@usableFromInline
internal let inserted: [Index]
@usableFromInline
internal let updated: [Index]
@usableFromInline
internal let moved: [(source: Index, target: Index)]
@usableFromInline
internal let metadata: Metadata

@inlinable
internal init(
deleted: [Index] = [],
inserted: [Index] = [],
updated: [Index] = [],
Expand All @@ -553,32 +564,46 @@ private struct DifferentiateResult<Index> {
}

/// A set of informations in middle of difference calculation.
private struct Trace<Index> {
var reference: Index?
var deleteOffset = 0
var isTracked = false
@usableFromInline
internal struct Trace<Index> {
@usableFromInline
internal var reference: Index?
@usableFromInline
internal var deleteOffset = 0
@usableFromInline
internal var isTracked = false

@inlinable
init() {}
}

/// The occurrences of element.
private enum Occurrence {
@usableFromInline
internal enum Occurrence {
case unique(index: Int)
case duplicate(reference: IndicesReference)
}

/// A mutable reference to indices of elements.
private final class IndicesReference {
private var indices: ContiguousArray<Int>
private var position = 0

init(_ indices: ContiguousArray<Int>) {
@usableFromInline
internal final class IndicesReference {
@usableFromInline
internal var indices: ContiguousArray<Int>
@usableFromInline
internal var position = 0

@inlinable
internal init(_ indices: ContiguousArray<Int>) {
self.indices = indices
}

func push(_ index: Int) {
@inlinable
internal func push(_ index: Int) {
indices.append(index)
}

func next() -> Int? {
@inlinable
internal func next() -> Int? {
guard position < indices.endIndex else {
return nil
}
Expand All @@ -588,23 +613,29 @@ private final class IndicesReference {
}

/// Dictionary key using UnsafePointer for performance optimization.
private struct TableKey<T: Hashable>: Hashable {
let hashValue: Int
private let pointer: UnsafePointer<T>

init(pointer: UnsafePointer<T>) {
@usableFromInline
internal struct TableKey<T: Hashable>: Hashable {
@usableFromInline
internal let hashValue: Int
@usableFromInline
internal let pointer: UnsafePointer<T>

@inlinable
internal init(pointer: UnsafePointer<T>) {
self.hashValue = pointer.pointee.hashValue
self.pointer = pointer
}

static func == (lhs: TableKey, rhs: TableKey) -> Bool {
@inlinable
internal static func == (lhs: TableKey, rhs: TableKey) -> Bool {
return lhs.hashValue == rhs.hashValue
&& (lhs.pointer.distance(to: rhs.pointer) == 0 || lhs.pointer.pointee == rhs.pointer.pointee)
}
}

private extension MutableCollection where Element: MutableCollection, Index == Int, Element.Index == Int {
subscript(path: ElementPath) -> Element.Element {
internal extension MutableCollection where Element: MutableCollection, Index == Int, Element.Index == Int {
@inlinable
internal subscript(path: ElementPath) -> Element.Element {
get { return self[path.section][path.element] }
set { self[path.section][path.element] = newValue }
}
Expand Down
63 changes: 52 additions & 11 deletions Sources/AnyDifferentiable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,27 @@
/// print(changeset.isEmpty) // prints "false"
public struct AnyDifferentiable: Differentiable {
/// The value wrapped by this instance.
public let base: Any
@inlinable
public var base: Any {
return box.base
}

/// A type-erased identifier value for difference calculation.
public let differenceIdentifier: AnyHashable
@inlinable
public var differenceIdentifier: AnyHashable {
return box.differenceIdentifier
}

private let isContentEqualTo: (AnyDifferentiable) -> Bool
@usableFromInline
internal let box: AnyDifferentiableBox

/// Creates a type-erased differentiable value that wraps the given instance.
///
/// - Parameters:
/// - base: A differentiable value to wrap.
@inlinable
public init<D: Differentiable>(_ base: D) {
self.base = base
self.differenceIdentifier = AnyHashable(base.differenceIdentifier)

self.isContentEqualTo = { source in
guard let sourceBase = source.base as? D else { return false }
return base.isContentEqual(to: sourceBase)
}
box = DifferentiableBox(base)
}

/// Indicate whether the content of `base` is equals to the content of the given source value.
Expand All @@ -51,8 +54,9 @@ public struct AnyDifferentiable: Differentiable {
///
/// - Returns: A Boolean value indicating whether the content of `base` is equals
/// to the content of `base` of the given source value.
@inlinable
public func isContentEqual(to source: AnyDifferentiable) -> Bool {
return isContentEqualTo(source)
return box.isContentEqual(to: source.box)
}
}

Expand All @@ -61,3 +65,40 @@ extension AnyDifferentiable: CustomDebugStringConvertible {
return "AnyDifferentiable(\(String(reflecting: base))"
}
}

@usableFromInline
internal protocol AnyDifferentiableBox {
var base: Any { get }
var differenceIdentifier: AnyHashable { get }

func isContentEqual(to source: AnyDifferentiableBox) -> Bool
}

@usableFromInline
internal struct DifferentiableBox<Base: Differentiable>: AnyDifferentiableBox {
@usableFromInline
internal let baseComponent: Base

@inlinable
internal var base: Any {
return baseComponent
}

@inlinable
internal var differenceIdentifier: AnyHashable {
return AnyHashable(baseComponent.differenceIdentifier)
}

@inlinable
internal init(_ base: Base) {
baseComponent = base
}

@inlinable
internal func isContentEqual(to source: AnyDifferentiableBox) -> Bool {
guard let sourceBase = source.base as? Base else {
return false
}
return baseComponent.isContentEqual(to: sourceBase)
}
}
Loading

0 comments on commit 403a3ab

Please sign in to comment.