From 851064e09d60d3e2e8555f155993a5bfecfb904e Mon Sep 17 00:00:00 2001 From: Christoffer Winterkvist Date: Sun, 3 Mar 2019 20:08:01 +0100 Subject: [PATCH 1/2] Refactor implementation to offer more customization - Add `compare` closure to the `DiffManager` for custom comparison. - Add `Diffable` protocol to override which value to use for diffing. --- Differific.xcodeproj/project.pbxproj | 8 +++++ Source/Shared/Algorithm.swift | 14 ++++---- Source/Shared/DiffManager.swift | 5 +-- Source/Shared/Diffable.swift | 5 +++ Tests/Shared/DiffManagerTests.swift | 48 ++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 Source/Shared/Diffable.swift diff --git a/Differific.xcodeproj/project.pbxproj b/Differific.xcodeproj/project.pbxproj index e51aeac..3eb5b86 100644 --- a/Differific.xcodeproj/project.pbxproj +++ b/Differific.xcodeproj/project.pbxproj @@ -46,6 +46,9 @@ BDBF80E8209252440002CE93 /* UICollectionViewExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80E5209252440002CE93 /* UICollectionViewExtensionsTests.swift */; }; BDBF80EA20927B080002CE93 /* NSCollectionViewExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80E920927B080002CE93 /* NSCollectionViewExtensionTests.swift */; }; BDBF80EC20927B360002CE93 /* NSTableViewExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80EB20927B360002CE93 /* NSTableViewExtensionsTests.swift */; }; + BDED0DC8222C57A800D7B46E /* Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDED0DC7222C57A800D7B46E /* Diffable.swift */; }; + BDED0DC9222C57A800D7B46E /* Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDED0DC7222C57A800D7B46E /* Diffable.swift */; }; + BDED0DCA222C57A800D7B46E /* Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDED0DC7222C57A800D7B46E /* Diffable.swift */; }; D284B1051F79038B00D94AF3 /* Differific.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D284B0FC1F79038B00D94AF3 /* Differific.framework */; }; D284B1381F7906DB00D94AF3 /* Info-iOS-Tests.plist in Resources */ = {isa = PBXBuildFile; fileRef = D284B12F1F7906DA00D94AF3 /* Info-iOS-Tests.plist */; }; D284B1391F7906DB00D94AF3 /* Info-tvOS-Tests.plist in Resources */ = {isa = PBXBuildFile; fileRef = D284B1301F7906DA00D94AF3 /* Info-tvOS-Tests.plist */; }; @@ -99,6 +102,7 @@ BDBF80E5209252440002CE93 /* UICollectionViewExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UICollectionViewExtensionsTests.swift; sourceTree = ""; }; BDBF80E920927B080002CE93 /* NSCollectionViewExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSCollectionViewExtensionTests.swift; sourceTree = ""; }; BDBF80EB20927B360002CE93 /* NSTableViewExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSTableViewExtensionsTests.swift; sourceTree = ""; }; + BDED0DC7222C57A800D7B46E /* Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Diffable.swift; sourceTree = ""; }; D284B0FC1F79038B00D94AF3 /* Differific.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Differific.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D284B1041F79038B00D94AF3 /* Differific-tvOS-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Differific-tvOS-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; D284B1181F79039F00D94AF3 /* Differific.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Differific.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -283,6 +287,7 @@ BD690D4F20DC143100E56E30 /* ArrayEntry.swift */, BDBF80C420923FD00002CE93 /* Change.swift */, BD690D4720DC13F700E56E30 /* Counter.swift */, + BDED0DC7222C57A800D7B46E /* Diffable.swift */, BDBF80C320923FD00002CE93 /* DiffManager.swift */, BDBF80C520923FD00002CE93 /* IndexPathManager.swift */, BD690D4B20DC141200E56E30 /* TableEntry.swift */, @@ -587,6 +592,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BDED0DCA222C57A800D7B46E /* Diffable.swift in Sources */, BDBF80CF20923FD00002CE93 /* IndexPathManager.swift in Sources */, BDBF80D220923FD00002CE93 /* Algorithm.swift in Sources */, BDBF80BC20923FB90002CE93 /* UICollectionView+Extensions.swift in Sources */, @@ -621,6 +627,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BDED0DC8222C57A800D7B46E /* Diffable.swift in Sources */, BDBF80CD20923FD00002CE93 /* IndexPathManager.swift in Sources */, BDBF80D020923FD00002CE93 /* Algorithm.swift in Sources */, BDBF80BB20923FB90002CE93 /* UICollectionView+Extensions.swift in Sources */, @@ -648,6 +655,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BDED0DC9222C57A800D7B46E /* Diffable.swift in Sources */, BDBF80CE20923FD00002CE93 /* IndexPathManager.swift in Sources */, BDBF80D120923FD00002CE93 /* Algorithm.swift in Sources */, BDBF80D820923FFA0002CE93 /* NSTableView+Extensions.swift in Sources */, diff --git a/Source/Shared/Algorithm.swift b/Source/Shared/Algorithm.swift index 71931ce..a988a4a 100644 --- a/Source/Shared/Algorithm.swift +++ b/Source/Shared/Algorithm.swift @@ -1,7 +1,7 @@ import Foundation class Algorithm { - public static func diff(old: [T], new: [T]) -> [Change] { + public static func diff(old: [T], new: [T], compare: (T,T) -> Bool) -> [Change] { if new.isEmpty { var changes = [Change]() changes.reserveCapacity(old.count) @@ -35,28 +35,30 @@ class Algorithm { // 1 Pass for element in new[0...].lazy { + let diffValue = (element as? Diffable)?.diffValue ?? element.hashValue let entry: TableEntry - if let tableEntry = table[element.hashValue] { + if let tableEntry = table[diffValue] { entry = tableEntry } else { entry = TableEntry() } - table[element.hashValue] = entry + table[diffValue] = entry entry.newCounter += 1 newArray.append(ArrayEntry(tableEntry: entry)) } // 2 Pass for element in old[0...].lazy { + let diffValue = (element as? Diffable)?.diffValue ?? element.hashValue let entry: TableEntry - if let tableEntry = table[element.hashValue] { + if let tableEntry = table[diffValue] { entry = tableEntry } else { entry = TableEntry() } - table[element.hashValue] = entry + table[diffValue] = entry entry.oldCounter += 1 entry.indexesInOld.append(offset) oldArray.append(ArrayEntry(tableEntry: entry)) @@ -135,7 +137,7 @@ class Algorithm { runningOffset += 1 } else { let oldIndex = element.indexInOther - if old[oldIndex] != new[offset] { + if !compare(old[oldIndex], new[offset]) { changes.append(Change(.update, item: old[oldIndex], index: oldIndex, diff --git a/Source/Shared/DiffManager.swift b/Source/Shared/DiffManager.swift index 17dee61..9489d9e 100644 --- a/Source/Shared/DiffManager.swift +++ b/Source/Shared/DiffManager.swift @@ -2,7 +2,8 @@ import Foundation public class DiffManager { public init() {} - public func diff(_ old: [T], _ new: [T]) -> [Change] { - return Algorithm.diff(old: old, new: new) + public func diff(_ old: [T], _ new: [T], + compare: (T,T) -> Bool = { $0 == $1 }) -> [Change] { + return Algorithm.diff(old: old, new: new, compare: compare) } } diff --git a/Source/Shared/Diffable.swift b/Source/Shared/Diffable.swift new file mode 100644 index 0000000..c25cbf2 --- /dev/null +++ b/Source/Shared/Diffable.swift @@ -0,0 +1,5 @@ +import Foundation + +public protocol Diffable { + var diffValue: Int { get } +} diff --git a/Tests/Shared/DiffManagerTests.swift b/Tests/Shared/DiffManagerTests.swift index 927fd1a..5f23c23 100644 --- a/Tests/Shared/DiffManagerTests.swift +++ b/Tests/Shared/DiffManagerTests.swift @@ -2,6 +2,13 @@ import XCTest import Differific class DiffManagerTests: XCTestCase { + struct MockDiffable: Hashable, Diffable { + let id: Int + var diffValue: Int { + return id + } + } + struct MockObject: Hashable { let id: Int let name: String @@ -148,6 +155,47 @@ class DiffManagerTests: XCTestCase { XCTAssertEqual(result.count, 5) } + func testComparator() { + let old = [ + MockObject(id: 0, name: "Foo"), + MockObject(id: 1, name: "Bar"), + MockObject(id: 2, name: "Baz") + ] + let new = [ + MockObject(id: 1, name: "Foo0"), + MockObject(id: 0, name: "Bar1"), + MockObject(id: 3, name: "Baz2") + ] + + let diffManager = DiffManager() + let changes = diffManager.diff(old, new, compare: { $0.id == $1.id }) + + XCTAssertEqual(changes[0].kind, .delete) + XCTAssertEqual(changes[1].kind, .move) + XCTAssertEqual(changes[2].kind, .insert) + } + + func testDiffable() { + let old = [ + MockDiffable(id: 0), + MockDiffable(id: 1), + MockDiffable(id: 2) + ] + + let new = [ + MockDiffable(id: 1), + MockDiffable(id: 0), + MockDiffable(id: 3) + ] + + let diffManager = DiffManager() + let changes = diffManager.diff(old, new) + + XCTAssertEqual(changes[0].kind, .delete) + XCTAssertEqual(changes[1].kind, .move) + XCTAssertEqual(changes[2].kind, .insert) + } + func testPerformance() { let diffManager = DiffManager() var old = [Int]() From 78ce95a2569ad6401fd62440fd0c7b62956547ae Mon Sep 17 00:00:00 2001 From: Christoffer Winterkvist Date: Sun, 3 Mar 2019 20:09:49 +0100 Subject: [PATCH 2/2] Create codecov.yml --- codecov.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..c93eb12 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,30 @@ +ignore: + - "Applications/Xcode.app/.*" + - "bin" + - "Example" + - "Tests" + - "Images" + - "Info" +coverage: + status: + project: + default: off + macOS: + flags: macOS + tvOS: + flags: tvOS + iOS: + flags: iOS +flags: + macOS: + paths: + - Source/macOS + - Source/Shared + tvOS: + paths: + - Source/iOS+tvOS + - Source/Shared + iOS: + paths: + - Source/iOS+tvOS + - Source/Shared