├── .github
├── FUNDING.yml
├── main.workflow
└── workflows
│ └── greetings.yml
├── .gitignore
├── CONTRIBUTING.md
├── DeepDiff.podspec
├── DeepDiff.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ ├── xcbaselines
│ └── D5B2E8A81C3A780C00C0327D.xcbaseline
│ │ ├── 6E48E6A6-6901-4268-8D4F-F5D7270313AE.plist
│ │ ├── 778C5D14-0A59-46CE-BDBD-F7094157D397.plist
│ │ └── Info.plist
│ └── xcschemes
│ ├── DeepDiff-iOS.xcscheme
│ ├── DeepDiff-macOS.xcscheme
│ ├── SwiftPackage-tvOS.xcscheme
│ └── SwiftPackage-watchOS.xcscheme
├── DeepDiffTests
├── HeckelTests.swift
├── Info-iOS-Tests.plist
├── Info-macOS-Tests.plist
├── Info-tvOS-Tests.plist
├── PerformanceTests.swift
├── User.swift
└── WagnerFischerTests.swift
├── Example
├── Benchmark
│ ├── Benchmark.xcodeproj
│ │ ├── project.pbxproj
│ │ └── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ ├── Benchmark.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── Benchmark
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ ├── Base.lproj
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── ViewController.swift
│ ├── Podfile
│ └── Podfile.lock
├── DeepDiffDemo
│ ├── DeepDiffDemo.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── DeepDiffDemo.xcscheme
│ ├── DeepDiffDemo.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── DeepDiffDemo
│ │ ├── Base.lproj
│ │ │ └── LaunchScreen.storyboard
│ │ ├── Info.plist
│ │ ├── Resources
│ │ │ └── Assets.xcassets
│ │ │ │ ├── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ │ │ ├── Contents.json
│ │ │ │ ├── collection.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── grid.png
│ │ │ │ └── table.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── list.png
│ │ └── Sources
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Collection+Extensions.swift
│ │ │ ├── CollectionViewCell.swift
│ │ │ ├── CollectionViewController.swift
│ │ │ ├── Color+Extensions.swift
│ │ │ ├── DataSet.swift
│ │ │ ├── DeepDiffDemo-Bridging-Header.h
│ │ │ ├── ExceptionCatcher.h
│ │ │ ├── TableViewCell.swift
│ │ │ └── TableViewController.swift
│ ├── Podfile
│ └── Podfile.lock
└── DeepDiffTexture
│ ├── DeepDiffTexture.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ ├── DeepDiffTexture.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ ├── DeepDiffTexture
│ ├── ASTableNodeExtension.swift
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── DataSet.swift
│ ├── Info.plist
│ ├── RootNode.swift
│ ├── TableCellNode.swift
│ ├── TextureTableController.swift
│ └── ViewController.swift
│ ├── Podfile
│ └── Podfile.lock
├── Info
├── Info-iOS.plist
├── Info-macOS.plist
├── Info-tvOS.plist
└── Info-watchOS.plist
├── LICENSE.md
├── Package.swift
├── README.md
├── Screenshots
├── Banner.png
├── benchmark3d.png
├── collection.gif
└── table.gif
└── Sources
├── Shared
├── Algorithms
│ ├── Heckel.swift
│ └── WagnerFischer.swift
├── Array+Extensions.swift
├── Change.swift
├── DeepDiff.swift
├── DiffAware.swift
└── MoveReducer.swift
└── iOS
├── IndexPathConverter.swift
├── UICollectionView+Extensions.swift
└── UITableView+Extensions.swift
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: onmyway133
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/main.workflow:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.github/workflows/greetings.yml:
--------------------------------------------------------------------------------
1 | name: Greetings
2 |
3 | on: [pull_request, issues]
4 |
5 | jobs:
6 | greeting:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/first-interaction@v1
10 | with:
11 | repo-token: ${{ secrets.GITHUB_TOKEN }}
12 | issue-message: 'Message that will be displayed on users'' first issue'
13 | pr-message: 'Message that will be displayed on users'' first pr'
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 | .AppleDouble
4 | .LSOverride
5 | Icon
6 | ._*
7 | .Spotlight-V100
8 | .Trashes
9 |
10 | # Xcode
11 | #
12 | build/
13 | *.pbxuser
14 | !default.pbxuser
15 | *.mode1v3
16 | !default.mode1v3
17 | *.mode2v3
18 | !default.mode2v3
19 | *.perspectivev3
20 | !default.perspectivev3
21 | xcuserdata
22 | *.xccheckout
23 | *.moved-aside
24 | DerivedData
25 | *.hmap
26 | *.ipa
27 | *.xcuserstate
28 |
29 | # CocoaPods
30 | Pods
31 |
32 | # Carthage
33 | Carthage
34 |
35 | # SPM
36 | .build/
37 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | GitHub Issues is for reporting bugs, discussing features and general feedback in **DeepDiff**. Be sure to check our [documentation](http://cocoadocs.org/docsets/DeepDiff), [FAQ](https://github.com/onmyway133/DeepDiff/wiki/FAQ) and [past issues](https://github.com/onmyway133/DeepDiff/issues?state=closed) before opening any new issues.
2 |
3 | If you are posting about a crash in your application, a stack trace is helpful, but additional context, in the form of code and explanation, is necessary to be of any use.
4 |
--------------------------------------------------------------------------------
/DeepDiff.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "DeepDiff"
3 | s.summary = "Amazingly incredible extraordinary lightning fast diffing in Swift"
4 | s.version = "2.2.0"
5 | s.homepage = "https://github.com/onmyway133/DeepDiff"
6 | s.license = 'MIT'
7 | s.author = { "Khoa Pham" => "onmyway133@gmail.com" }
8 | s.source = {
9 | :git => "https://github.com/onmyway133/DeepDiff.git",
10 | :tag => s.version.to_s
11 | }
12 | s.social_media_url = 'https://twitter.com/onmyway133'
13 |
14 | s.ios.deployment_target = '9.0'
15 | s.osx.deployment_target = '10.10'
16 | s.tvos.deployment_target = '9.2'
17 | s.watchos.deployment_target = "6.0"
18 |
19 | s.requires_arc = true
20 |
21 | s.ios.source_files = 'Sources/{iOS,Shared}/**/*'
22 | s.osx.source_files = 'Sources/{Shared}/**/*'
23 | s.tvos.source_files = 'Sources/{iOS,Shared}/**/*'
24 | s.watchos.source_files = 'Sources/Shared/**/*'
25 |
26 | s.ios.framework = "UIKit"
27 | s.swift_version = '5.0'
28 | end
29 |
--------------------------------------------------------------------------------
/DeepDiff.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/DeepDiff.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/DeepDiff.xcodeproj/xcshareddata/xcbaselines/D5B2E8A81C3A780C00C0327D.xcbaseline/6E48E6A6-6901-4268-8D4F-F5D7270313AE.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | classNames
6 |
7 | PerformanceTests
8 |
9 | test100000Items_AllDelete()
10 |
11 | com.apple.XCTPerformanceMetric_WallClockTime
12 |
13 | baselineAverage
14 | 0.05777
15 | baselineIntegrationDisplayName
16 | 31. okt. 2017, 11:00:42
17 |
18 |
19 | test100000Items_AllInsert()
20 |
21 | com.apple.XCTPerformanceMetric_WallClockTime
22 |
23 | baselineAverage
24 | 0.060873
25 | baselineIntegrationDisplayName
26 | 31. okt. 2017, 11:01:02
27 |
28 |
29 | test10000Items_AllInsert()
30 |
31 | com.apple.XCTPerformanceMetric_WallClockTime
32 |
33 | baselineAverage
34 | 0.0065879
35 | baselineIntegrationDisplayName
36 | 31. okt. 2017, 10:59:26
37 |
38 |
39 | test1000Items_Add100()
40 |
41 | com.apple.XCTPerformanceMetric_WallClockTime
42 |
43 | baselineAverage
44 | 0.5
45 | baselineIntegrationDisplayName
46 | 31. okt. 2017, 11:03:22
47 |
48 |
49 | test1000Items_Delete100()
50 |
51 | com.apple.XCTPerformanceMetric_WallClockTime
52 |
53 | baselineAverage
54 | 0.5
55 | baselineIntegrationDisplayName
56 | 31. okt. 2017, 11:01:38
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/DeepDiff.xcodeproj/xcshareddata/xcbaselines/D5B2E8A81C3A780C00C0327D.xcbaseline/778C5D14-0A59-46CE-BDBD-F7094157D397.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | classNames
6 |
7 | PerformanceTests
8 |
9 | test1000Items_Add100()
10 |
11 | com.apple.XCTPerformanceMetric_WallClockTime
12 |
13 | baselineAverage
14 | 0.5
15 | baselineIntegrationDisplayName
16 | 31. okt. 2017, 10:51:38
17 |
18 |
19 | test1000Items_Delete100_Add100()
20 |
21 | com.apple.XCTPerformanceMetric_WallClockTime
22 |
23 | baselineAverage
24 | 0.5
25 | baselineIntegrationDisplayName
26 | 31. okt. 2017, 10:30:46
27 |
28 |
29 | test100Items_Delete50_Add50()
30 |
31 | com.apple.XCTPerformanceMetric_WallClockTime
32 |
33 | baselineAverage
34 | 0.029626
35 | baselineIntegrationDisplayName
36 | 31. okt. 2017, 10:30:05
37 |
38 |
39 | test4Items_Delete2_Add2()
40 |
41 | com.apple.XCTPerformanceMetric_WallClockTime
42 |
43 | baselineAverage
44 | 0.0001084
45 | baselineIntegrationDisplayName
46 | 31. okt. 2017, 10:26:53
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/DeepDiff.xcodeproj/xcshareddata/xcbaselines/D5B2E8A81C3A780C00C0327D.xcbaseline/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | runDestinationsByUUID
6 |
7 | 6E48E6A6-6901-4268-8D4F-F5D7270313AE
8 |
9 | localComputer
10 |
11 | busSpeedInMHz
12 | 100
13 | cpuCount
14 | 1
15 | cpuKind
16 | Intel Core i7
17 | cpuSpeedInMHz
18 | 2500
19 | logicalCPUCoresPerPackage
20 | 8
21 | modelCode
22 | MacBookPro11,5
23 | physicalCPUCoresPerPackage
24 | 4
25 | platformIdentifier
26 | com.apple.platform.macosx
27 |
28 | targetArchitecture
29 | x86_64
30 | targetDevice
31 |
32 | modelCode
33 | iPhone10,3
34 | platformIdentifier
35 | com.apple.platform.iphonesimulator
36 |
37 |
38 | 778C5D14-0A59-46CE-BDBD-F7094157D397
39 |
40 | localComputer
41 |
42 | busSpeedInMHz
43 | 100
44 | cpuCount
45 | 1
46 | cpuKind
47 | Intel Core i7
48 | cpuSpeedInMHz
49 | 2500
50 | logicalCPUCoresPerPackage
51 | 8
52 | modelCode
53 | MacBookPro11,5
54 | physicalCPUCoresPerPackage
55 | 4
56 | platformIdentifier
57 | com.apple.platform.macosx
58 |
59 | targetArchitecture
60 | x86_64
61 | targetDevice
62 |
63 | modelCode
64 | iPhone10,5
65 | platformIdentifier
66 | com.apple.platform.iphonesimulator
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/DeepDiff.xcodeproj/xcshareddata/xcschemes/DeepDiff-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
51 |
52 |
62 |
63 |
69 |
70 |
71 |
72 |
78 |
79 |
85 |
86 |
87 |
88 |
90 |
91 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/DeepDiff.xcodeproj/xcshareddata/xcschemes/DeepDiff-macOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/DeepDiff.xcodeproj/xcshareddata/xcschemes/SwiftPackage-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
51 |
52 |
62 |
63 |
69 |
70 |
71 |
72 |
78 |
79 |
85 |
86 |
87 |
88 |
90 |
91 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/DeepDiff.xcodeproj/xcshareddata/xcschemes/SwiftPackage-watchOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/DeepDiffTests/HeckelTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HeckelTests.swift
3 | // DeepDiff
4 | //
5 | // Created by Khoa Pham.
6 | // Copyright © 2018 Khoa Pham. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import DeepDiff
11 |
12 | class HeckelTests: XCTestCase {
13 | func testEmpty() {
14 | let old: [String] = []
15 | let new: [String] = []
16 | let changes = diff(old: old, new: new)
17 | XCTAssertEqual(changes.count, 0)
18 | }
19 |
20 | func testAllInsert() {
21 | let old = Array("")
22 | let new = Array("abc")
23 | let changes = diff(old: old, new: new)
24 | XCTAssertEqual(changes.count, 3)
25 |
26 | XCTAssertEqual(changes[0].insert?.item, "a")
27 | XCTAssertEqual(changes[0].insert?.index, 0)
28 |
29 | XCTAssertEqual(changes[1].insert?.item, "b")
30 | XCTAssertEqual(changes[1].insert?.index, 1)
31 |
32 | XCTAssertEqual(changes[2].insert?.item, "c")
33 | XCTAssertEqual(changes[2].insert?.index, 2)
34 | }
35 |
36 | func testAllDelete() {
37 | let old = Array("abc")
38 | let new = Array("")
39 | let changes = diff(old: old, new: new)
40 | XCTAssertEqual(changes.count, 3)
41 |
42 | XCTAssertEqual(changes[0].delete?.item, "a")
43 | XCTAssertEqual(changes[0].delete?.index, 0)
44 |
45 | XCTAssertEqual(changes[1].delete?.item, "b")
46 | XCTAssertEqual(changes[1].delete?.index, 1)
47 |
48 | XCTAssertEqual(changes[2].delete?.item, "c")
49 | XCTAssertEqual(changes[2].delete?.index, 2)
50 | }
51 |
52 | func testReplace() {
53 | let old = Array("abc")
54 | let new = Array("aBc")
55 |
56 | let changes = diff(old: old, new: new)
57 | XCTAssertEqual(changes.count, 2)
58 |
59 | XCTAssertNotNil(changes[0].delete)
60 | XCTAssertNotNil(changes[1].insert)
61 | }
62 |
63 | func testReplace2() {
64 | let old = [
65 | User(id: 1, name: "a"),
66 | User(id: 2, name: "b"),
67 | User(id: 3, name: "c"),
68 | ]
69 |
70 | let new = [
71 | User(id: 1, name: "a"),
72 | User(id: 2, name: "b2"),
73 | User(id: 3, name: "c"),
74 | ]
75 |
76 | let changes = diffWF(old: old, new: new)
77 | XCTAssertEqual(changes.count, 1)
78 |
79 | XCTAssertNotNil(changes[0].replace)
80 | }
81 |
82 | func testReplace3() {
83 | let old = [
84 | User(id: 1, name: "Captain America"),
85 | User(id: 2, name: "Captain Marvel"),
86 | User(id: 3, name: "Thor"),
87 | ]
88 |
89 | let new = [
90 | User(id: 1, name: "Captain America"),
91 | User(id: 2, name: "The Binary"),
92 | User(id: 3, name: "Thor"),
93 | ]
94 |
95 | let changes = diffWF(old: old, new: new)
96 | XCTAssertEqual(changes.count, 1)
97 |
98 | XCTAssertNotNil(changes[0].replace)
99 | }
100 |
101 | func testAllReplace() {
102 | let old = Array("abc")
103 | let new = Array("ABC")
104 |
105 | let changes = diff(old: old, new: new)
106 | XCTAssertEqual(changes.count, 6)
107 |
108 | XCTAssertNotNil(changes[0].delete)
109 | XCTAssertNotNil(changes[1].delete)
110 | XCTAssertNotNil(changes[2].delete)
111 |
112 | XCTAssertNotNil(changes[3].insert)
113 | XCTAssertNotNil(changes[4].insert)
114 | XCTAssertNotNil(changes[5].insert)
115 | }
116 |
117 | func testSamePrefix() {
118 | let old = Array("abc")
119 | let new = Array("aB")
120 | let changes = diff(old: old, new: new)
121 | XCTAssertEqual(changes.count, 3)
122 |
123 | XCTAssertNotNil(changes[0].delete)
124 | XCTAssertNotNil(changes[1].delete)
125 | XCTAssertNotNil(changes[2].insert)
126 | }
127 |
128 | func testReversed() {
129 | let old = Array("abc")
130 | let new = Array("cba")
131 | let changes = diff(old: old, new: new)
132 | XCTAssertEqual(changes.count, 2)
133 |
134 | XCTAssertNotNil(changes[0].move)
135 | XCTAssertNotNil(changes[1].move)
136 | }
137 |
138 | func testInsert() {
139 | let old = Array("a")
140 | let new = Array("ba")
141 | let changes = diff(old: old, new: new)
142 | XCTAssertEqual(changes.count, 1)
143 |
144 | XCTAssertNotNil(changes[0].insert)
145 | }
146 |
147 | func testSmallChangesAtEdges() {
148 | let old = Array("sitting")
149 | let new = Array("kitten")
150 | let changes = diff(old: old, new: new)
151 | XCTAssertEqual(changes.count, 5)
152 |
153 | XCTAssertNotNil(changes[0].delete)
154 | XCTAssertNotNil(changes[1].delete)
155 | XCTAssertNotNil(changes[2].delete)
156 | XCTAssertNotNil(changes[3].insert)
157 | XCTAssertNotNil(changes[4].insert)
158 | }
159 |
160 | func testSamePostfix() {
161 | let old = Array("abcdef")
162 | let new = Array("def")
163 |
164 | let changes = diff(old: old, new: new)
165 | XCTAssertEqual(changes.count, 3)
166 |
167 | XCTAssertEqual(changes[0].delete?.item, "a")
168 | XCTAssertEqual(changes[0].delete?.index, 0)
169 |
170 | XCTAssertEqual(changes[1].delete?.item, "b")
171 | XCTAssertEqual(changes[1].delete?.index, 1)
172 |
173 | XCTAssertEqual(changes[2].delete?.item, "c")
174 | XCTAssertEqual(changes[2].delete?.index, 2)
175 | }
176 |
177 | func testShift() {
178 | let old = Array("abcd")
179 | let new = Array("cdef")
180 |
181 | let changes = diff(old: old, new: new)
182 | XCTAssertEqual(changes.count, 4)
183 |
184 | XCTAssertEqual(changes[0].delete?.item, "a")
185 | XCTAssertEqual(changes[0].delete?.index, 0)
186 |
187 | XCTAssertEqual(changes[1].delete?.item, "b")
188 | XCTAssertEqual(changes[1].delete?.index, 1)
189 |
190 | XCTAssertEqual(changes[2].insert?.item, "e")
191 | XCTAssertEqual(changes[2].insert?.index, 2)
192 |
193 | XCTAssertEqual(changes[3].insert?.item, "f")
194 | XCTAssertEqual(changes[3].insert?.index, 3)
195 | }
196 |
197 | func testReplaceWholeNewWord() {
198 | let old = Array("abc")
199 | let new = Array("d")
200 |
201 | let changes = diff(old: old, new: new)
202 | XCTAssertEqual(changes.count, 4)
203 |
204 | XCTAssertNotNil(changes[0].delete)
205 | XCTAssertNotNil(changes[1].delete)
206 | XCTAssertNotNil(changes[2].delete)
207 | XCTAssertNotNil(changes[3].insert)
208 | }
209 |
210 | func testReplace1Character() {
211 | let old = Array("a")
212 | let new = Array("b")
213 |
214 | let changes = diff(old: old, new: new)
215 | XCTAssertEqual(changes.count, 2)
216 |
217 | XCTAssertNotNil(changes[0].delete)
218 | XCTAssertNotNil(changes[1].insert)
219 | }
220 |
221 | func testObject() {
222 | let old = [
223 | User(id: 1, name: "a"),
224 | User(id: 2, name: "b")
225 | ]
226 |
227 | let new = [
228 | User(id: 1, name: "a"),
229 | User(id: 2, name: "a"),
230 | User(id: 3, name: "c")
231 | ]
232 |
233 | let changes = diff(old: old, new: new)
234 | XCTAssertEqual(changes.count, 2)
235 |
236 | XCTAssertNotNil(changes[0].replace)
237 | XCTAssertNotNil(changes[1].insert)
238 | }
239 |
240 | func testMoveWithInsertDelete() {
241 | let old = Array("12345")
242 | let new = Array("15234")
243 |
244 | let changes = diff(old: old, new: new)
245 | XCTAssertEqual(changes.count, 4)
246 |
247 | XCTAssertNotNil(changes[0].move)
248 | XCTAssertNotNil(changes[1].move)
249 | XCTAssertNotNil(changes[2].move)
250 | XCTAssertNotNil(changes[3].move)
251 | }
252 |
253 | func testMoveWithDeleteInsert() {
254 | let old = Array("15234")
255 | let new = Array("12345")
256 |
257 | let changes = diff(old: old, new: new)
258 | XCTAssertEqual(changes.count, 4)
259 |
260 | XCTAssertNotNil(changes[0].move)
261 | XCTAssertNotNil(changes[1].move)
262 | XCTAssertNotNil(changes[2].move)
263 | XCTAssertNotNil(changes[3].move)
264 | }
265 |
266 | func testMoveWithReplaceMoveReplace() {
267 | let old = Array("34152")
268 | let new = Array("51324")
269 |
270 | let changes = diff(old: old, new: new)
271 | XCTAssertEqual(changes.count, 5)
272 |
273 | XCTAssertNotNil(changes[0].move)
274 | XCTAssertNotNil(changes[1].move)
275 | XCTAssertNotNil(changes[2].move)
276 | XCTAssertNotNil(changes[3].move)
277 | XCTAssertNotNil(changes[4].move)
278 | }
279 |
280 | func testInt() {
281 | let old = Array("321")
282 | let new = Array("143")
283 |
284 | let changes = diff(old: old, new: new)
285 | XCTAssertEqual(changes.count, 4)
286 |
287 | XCTAssertNotNil(changes[0].delete)
288 | XCTAssertNotNil(changes[1].move)
289 | XCTAssertNotNil(changes[2].insert)
290 | XCTAssertNotNil(changes[3].move)
291 | }
292 |
293 | func testDeleteUntilOne() {
294 | let old = Array("abc")
295 | let new = Array("a")
296 |
297 | let changes = diff(old: old, new: new)
298 | XCTAssertEqual(changes.count, 2)
299 |
300 | XCTAssertEqual(changes[0].delete?.item, "b")
301 | XCTAssertEqual(changes[0].delete?.index, 1)
302 |
303 | XCTAssertEqual(changes[1].delete?.item, "c")
304 | XCTAssertEqual(changes[1].delete?.index, 2)
305 | }
306 |
307 | func testReplaceInsertReplaceDelete() {
308 | let old = Array("1302")
309 | let new = Array("0231")
310 |
311 | let changes = diff(old: old, new: new)
312 | XCTAssertEqual(changes.count, 4)
313 |
314 | XCTAssertNotNil(changes[0].move)
315 | XCTAssertNotNil(changes[1].move)
316 | XCTAssertNotNil(changes[2].move)
317 | XCTAssertNotNil(changes[3].move)
318 | }
319 |
320 | func testReplaceMoveReplace() {
321 | let old = Array("2013")
322 | let new = Array("1302")
323 |
324 | let changes = diff(old: old, new: new)
325 | XCTAssertEqual(changes.count, 4)
326 |
327 | XCTAssertNotNil(changes[0].move)
328 | XCTAssertNotNil(changes[1].move)
329 | XCTAssertNotNil(changes[2].move)
330 | XCTAssertNotNil(changes[3].move)
331 | }
332 | }
333 |
--------------------------------------------------------------------------------
/DeepDiffTests/Info-iOS-Tests.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/DeepDiffTests/Info-macOS-Tests.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/DeepDiffTests/Info-tvOS-Tests.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/DeepDiffTests/PerformanceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PerformanceTests.swift
3 | // DeepDiff
4 | //
5 | // Created by Khoa Pham.
6 | // Copyright © 2018 Khoa Pham. All rights reserved.
7 | //
8 | import XCTest
9 | import DeepDiff
10 |
11 | class PerformanceTests: XCTestCase {
12 | func test4Items_Replace2() {
13 | let data = generate(count: 4, removeRange: 0..<2, addRange: 2..<4)
14 |
15 | measure {
16 | let changes = diff(old: data.old, new: data.new)
17 | XCTAssertEqual(changes.count, 4)
18 | }
19 | }
20 |
21 | func test100Items_Replace50() {
22 | let data = generate(count: 100, removeRange: 0..<50, addRange: 50..<100)
23 |
24 | measure {
25 | let changes = diff(old: data.old, new: data.new)
26 | XCTAssertEqual(changes.count, 100)
27 | }
28 | }
29 |
30 | func test1000Items_Replace100() {
31 | let data = generate(count: 1000, removeRange: 100..<200, addRange: 799..<899)
32 |
33 | measure {
34 | let changes = diff(old: data.old, new: data.new)
35 | XCTAssertEqual(changes.count, 200)
36 | }
37 | }
38 |
39 | func test1000Items_Delete100() {
40 | let data = generate(count: 1000, removeRange: 100..<200)
41 |
42 | measure {
43 | let changes = diff(old: data.old, new: data.new)
44 | XCTAssertEqual(changes.count, 100)
45 | }
46 | }
47 |
48 | func test1000Items_Add100() {
49 | let data = generate(count: 1000, addRange: 999..<1099)
50 |
51 | measure {
52 | let changes = diff(old: data.old, new: data.new)
53 | XCTAssertEqual(changes.count, 100)
54 | }
55 | }
56 |
57 | func test10000Items_Delete1000() {
58 | let data = generate(count: 10000, removeRange: 1000..<2000)
59 |
60 | measure {
61 | let changes = diff(old: data.old, new: data.new)
62 | XCTAssertEqual(changes.count, 1000)
63 | }
64 | }
65 |
66 | func test100000Items_AllInsert() {
67 | let old = [String]()
68 | let new = Array(repeatElement(UUID().uuidString, count: 100000))
69 |
70 | measure {
71 | let changes = diff(old: old, new: new)
72 | XCTAssertEqual(changes.count, 100000)
73 | }
74 | }
75 |
76 | func test100000Items_AllDelete() {
77 | let old = Array(repeatElement(UUID().uuidString, count: 10000))
78 | let new = [String]()
79 |
80 | measure {
81 | let changes = diff(old: old, new: new)
82 | XCTAssertEqual(changes.count, 10000)
83 | }
84 | }
85 |
86 | // MARK: - Helper
87 | func _testCompareManyStrings() {
88 | let old = Array(0..<10000).map { _ in
89 | return UUID().uuidString
90 | }
91 |
92 | let new = Array(0..<10000).map { _ in
93 | return UUID().uuidString
94 | }
95 |
96 | measure {
97 | old.forEach { oldItem in
98 | new.forEach { newItem in
99 | if oldItem == newItem {
100 |
101 | } else {
102 |
103 | }
104 | }
105 | }
106 | }
107 | }
108 |
109 | func _testCompareManyInts() {
110 | let old = Array(0..<10000).map { _ in
111 | return arc4random()
112 | }
113 |
114 | let new = Array(0..<10000).map { _ in
115 | return arc4random()
116 | }
117 |
118 | measure {
119 | old.forEach { oldItem in
120 | new.forEach { newItem in
121 | if oldItem == newItem {
122 |
123 | } else {
124 |
125 | }
126 | }
127 | }
128 | }
129 | }
130 |
131 | /// Generate new by removing some items from old
132 | /// Use UUID to generate all same items, because of repeating
133 | /// If adding, add more items to new with new generated UUID
134 | func generate(count: Int, removeRange: Range? = nil, addRange: Range? = nil)
135 | -> (old: Array, new: Array) {
136 |
137 | let old = Array(repeating: UUID().uuidString, count: count)
138 | var new = old
139 |
140 | if let removeRange = removeRange {
141 | new.removeSubrange(removeRange)
142 | }
143 |
144 | if let addRange = addRange {
145 | new.insert(
146 | contentsOf: Array(repeating: UUID().uuidString, count: addRange.count),
147 | at: addRange.lowerBound
148 | )
149 | }
150 |
151 | return (old: old, new: new)
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/DeepDiffTests/User.swift:
--------------------------------------------------------------------------------
1 | //
2 | // User.swift
3 | // DeepDiff
4 | //
5 | // Created by Khoa Pham.
6 | // Copyright © 2018 Khoa Pham. All rights reserved.
7 | //
8 |
9 | import DeepDiff
10 |
11 | struct User: Equatable {
12 | let id: Int
13 | let name: String
14 | }
15 |
16 | extension User: DiffAware {
17 | var diffId: Int {
18 | return id
19 | }
20 |
21 | static func compareContent(_ a: User, _ b: User) -> Bool {
22 | return a.name == b.name
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/DeepDiffTests/WagnerFischerTests.swift:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // WagnerFischerTests.swift
4 | // DeepDiff
5 | //
6 | // Created by Khoa Pham.
7 | // Copyright © 2018 Khoa Pham. All rights reserved.
8 | //
9 |
10 | import XCTest
11 | import DeepDiff
12 |
13 | public func diffWF(old: [T], new: [T]) -> [Change] {
14 | let wf = WagnerFischer()
15 | return wf.diff(old: old, new: new)
16 | }
17 |
18 | public func diffWFReduceMove(old: [T], new: [T]) -> [Change] {
19 | let wf = WagnerFischer(reduceMove: true)
20 | return wf.diff(old: old, new: new)
21 | }
22 |
23 | class WagnerFischerTests: XCTestCase {
24 | func testEmpty() {
25 | let old: [String] = []
26 | let new: [String] = []
27 | let changes = diffWF(old: old, new: new)
28 | XCTAssertEqual(changes.count, 0)
29 | }
30 |
31 | func testAllInsert() {
32 | let old = Array("")
33 | let new = Array("abc")
34 | let changes = diff(old: old, new: new)
35 | XCTAssertEqual(changes.count, 3)
36 |
37 | XCTAssertEqual(changes[0].insert?.item, "a")
38 | XCTAssertEqual(changes[0].insert?.index, 0)
39 |
40 | XCTAssertEqual(changes[1].insert?.item, "b")
41 | XCTAssertEqual(changes[1].insert?.index, 1)
42 |
43 | XCTAssertEqual(changes[2].insert?.item, "c")
44 | XCTAssertEqual(changes[2].insert?.index, 2)
45 | }
46 |
47 | func testAllDelete() {
48 | let old = Array("abc")
49 | let new = Array("")
50 | let changes = diffWF(old: old, new: new)
51 | XCTAssertEqual(changes.count, 3)
52 |
53 | XCTAssertEqual(changes[0].delete?.item, "a")
54 | XCTAssertEqual(changes[0].delete?.index, 0)
55 |
56 | XCTAssertEqual(changes[1].delete?.item, "b")
57 | XCTAssertEqual(changes[1].delete?.index, 1)
58 |
59 | XCTAssertEqual(changes[2].delete?.item, "c")
60 | XCTAssertEqual(changes[2].delete?.index, 2)
61 | }
62 |
63 | func testAllReplace() {
64 | let old = Array("abc")
65 | let new = Array("ABC")
66 |
67 | let changes = diffWF(old: old, new: new)
68 | XCTAssertEqual(changes.count, 3)
69 |
70 | XCTAssertEqual(changes[0].replace?.oldItem, "a")
71 | XCTAssertEqual(changes[0].replace?.newItem, "A")
72 | XCTAssertEqual(changes[0].replace?.index, 0)
73 |
74 | XCTAssertEqual(changes[1].replace?.oldItem, "b")
75 | XCTAssertEqual(changes[1].replace?.newItem, "B")
76 | XCTAssertEqual(changes[1].replace?.index, 1)
77 |
78 | XCTAssertEqual(changes[2].replace?.oldItem, "c")
79 | XCTAssertEqual(changes[2].replace?.newItem, "C")
80 | XCTAssertEqual(changes[2].replace?.index, 2)
81 | }
82 |
83 | func testSamePrefix() {
84 | let old = Array("abc")
85 | let new = Array("aB")
86 | let changes = diffWF(old: old, new: new)
87 | XCTAssertEqual(changes.count, 2)
88 |
89 | XCTAssertEqual(changes[0].replace?.oldItem, "b")
90 | XCTAssertEqual(changes[0].replace?.newItem, "B")
91 | XCTAssertEqual(changes[0].replace?.index, 1)
92 |
93 | XCTAssertEqual(changes[1].delete?.item, "c")
94 | XCTAssertEqual(changes[1].delete?.index, 2)
95 | }
96 |
97 | func testReversed() {
98 | let old = Array("abc")
99 | let new = Array("cba")
100 | let changes = diffWF(old: old, new: new)
101 | XCTAssertEqual(changes.count, 2)
102 |
103 | XCTAssertEqual(changes[0].replace?.oldItem, "a")
104 | XCTAssertEqual(changes[0].replace?.newItem, "c")
105 | XCTAssertEqual(changes[0].replace?.index, 0)
106 |
107 | XCTAssertEqual(changes[1].replace?.oldItem, "c")
108 | XCTAssertEqual(changes[1].replace?.newItem, "a")
109 | XCTAssertEqual(changes[1].replace?.index, 2)
110 | }
111 |
112 | func testSmallChangesAtEdges() {
113 | let old = Array("sitting")
114 | let new = Array("kitten")
115 | let changes = diffWF(old: old, new: new)
116 | XCTAssertEqual(changes.count, 3)
117 |
118 | XCTAssertEqual(changes[0].replace?.oldItem, "s")
119 | XCTAssertEqual(changes[0].replace?.newItem, "k")
120 | XCTAssertEqual(changes[0].replace?.index, 0)
121 |
122 | XCTAssertEqual(changes[1].replace?.oldItem, "i")
123 | XCTAssertEqual(changes[1].replace?.newItem, "e")
124 | XCTAssertEqual(changes[1].replace?.index, 4)
125 |
126 | XCTAssertEqual(changes[2].delete?.item, "g")
127 | XCTAssertEqual(changes[2].delete?.index, 6)
128 | }
129 |
130 | func testSamePostfix() {
131 | let old = Array("abcdef")
132 | let new = Array("def")
133 |
134 | let changes = diffWF(old: old, new: new)
135 | XCTAssertEqual(changes.count, 3)
136 |
137 | XCTAssertEqual(changes[0].delete?.item, "a")
138 | XCTAssertEqual(changes[0].delete?.index, 0)
139 |
140 | XCTAssertEqual(changes[1].delete?.item, "b")
141 | XCTAssertEqual(changes[1].delete?.index, 1)
142 |
143 | XCTAssertEqual(changes[2].delete?.item, "c")
144 | XCTAssertEqual(changes[2].delete?.index, 2)
145 | }
146 |
147 | func testKitKat() {
148 | let old = Array("kit")
149 | let new = Array("kat")
150 |
151 | let changes = diffWF(old: old, new: new)
152 | XCTAssertEqual(changes.count, 1)
153 | }
154 |
155 | func testShift() {
156 | let old = Array("abcd")
157 | let new = Array("cdef")
158 |
159 | let changes = diffWF(old: old, new: new)
160 | XCTAssertEqual(changes.count, 4)
161 |
162 | XCTAssertEqual(changes[0].delete?.item, "a")
163 | XCTAssertEqual(changes[0].delete?.index, 0)
164 |
165 | XCTAssertEqual(changes[1].delete?.item, "b")
166 | XCTAssertEqual(changes[1].delete?.index, 1)
167 |
168 | XCTAssertEqual(changes[2].insert?.item, "e")
169 | XCTAssertEqual(changes[2].insert?.index, 2)
170 |
171 | XCTAssertEqual(changes[3].insert?.item, "f")
172 | XCTAssertEqual(changes[3].insert?.index, 3)
173 | }
174 |
175 | func testReplaceWholeNewWord() {
176 | let old = Array("abc")
177 | let new = Array("d")
178 |
179 | let changes = diffWF(old: old, new: new)
180 | XCTAssertEqual(changes.count, 3)
181 | }
182 |
183 | func testReplace1Character() {
184 | let old = Array("a")
185 | let new = Array("b")
186 |
187 | let changes = diffWF(old: old, new: new)
188 | XCTAssertEqual(changes.count, 1)
189 |
190 | XCTAssertEqual(changes[0].replace?.oldItem, "a")
191 | XCTAssertEqual(changes[0].replace?.newItem, "b")
192 | XCTAssertEqual(changes[0].replace?.index, 0)
193 | }
194 |
195 | func testObject() {
196 | let old = [
197 | User(id: 1, name: "a"),
198 | User(id: 2, name: "b")
199 | ]
200 |
201 | let new = [
202 | User(id: 1, name: "a"),
203 | User(id: 2, name: "a"),
204 | User(id: 3, name: "c")
205 | ]
206 |
207 | let changes = diffWF(old: old, new: new)
208 | XCTAssertEqual(changes.count, 2)
209 |
210 | XCTAssertEqual(changes[0].replace?.oldItem, User(id: 2, name: "b"))
211 | XCTAssertEqual(changes[0].replace?.newItem, User(id: 2, name: "a"))
212 | XCTAssertEqual(changes[0].replace?.index, 1)
213 |
214 | XCTAssertEqual(changes[1].insert?.item, User(id: 3, name: "c"))
215 | XCTAssertEqual(changes[1].insert?.index, 2)
216 | }
217 |
218 | func testObjectReplace() {
219 | let old = [
220 | User(id: 1, name: "a"),
221 | User(id: 2, name: "b"),
222 | User(id: 3, name: "c"),
223 | ]
224 |
225 | let new = [
226 | User(id: 1, name: "a"),
227 | User(id: 2, name: "b2"),
228 | User(id: 3, name: "c"),
229 | ]
230 |
231 | let changes = diffWF(old: old, new: new)
232 | XCTAssertEqual(changes.count, 1)
233 |
234 | XCTAssertNotNil(changes[0].replace)
235 | }
236 |
237 | func testMoveWithInsertDelete() {
238 | let old = Array("12345")
239 | let new = Array("15234")
240 |
241 | let changes = diffWFReduceMove(old: old, new: new)
242 | XCTAssertEqual(changes.count, 1)
243 |
244 | XCTAssertEqual(changes[0].move?.item, "5")
245 | XCTAssertEqual(changes[0].move?.fromIndex, 4)
246 | XCTAssertEqual(changes[0].move?.toIndex, 1)
247 | }
248 |
249 | func testMoveWithDeleteInsert() {
250 | let old = Array("15234")
251 | let new = Array("12345")
252 |
253 | let changes = diffWFReduceMove(old: old, new: new)
254 | XCTAssertEqual(changes.count, 1)
255 |
256 | XCTAssertEqual(changes[0].move?.item, "5")
257 | XCTAssertEqual(changes[0].move?.fromIndex, 1)
258 | XCTAssertEqual(changes[0].move?.toIndex, 4)
259 | }
260 |
261 | func testMoveWithReplaceMoveReplace() {
262 | let old = Array("34152")
263 | let new = Array("51324")
264 |
265 | let changes = diffWFReduceMove(old: old, new: new)
266 | XCTAssertEqual(changes.count, 3)
267 |
268 | XCTAssertNotNil(changes[0].replace)
269 | XCTAssertNotNil(changes[1].move)
270 | XCTAssertNotNil(changes[2].replace)
271 | }
272 |
273 | func testInt() {
274 | let old = Array("321")
275 | let new = Array("143")
276 |
277 | let changes = diffWFReduceMove(old: old, new: new)
278 | XCTAssertEqual(changes.count, 3)
279 |
280 | XCTAssertNotNil(changes[0].replace)
281 | XCTAssertNotNil(changes[1].replace)
282 | XCTAssertNotNil(changes[2].replace)
283 | }
284 |
285 | func testDeleteUntilOne() {
286 | let old = Array("abc")
287 | let new = Array("a")
288 |
289 | let changes = diffWFReduceMove(old: old, new: new)
290 | XCTAssertEqual(changes.count, 2)
291 |
292 | XCTAssertEqual(changes[0].delete?.item, "b")
293 | XCTAssertEqual(changes[0].delete?.index, 1)
294 |
295 | XCTAssertEqual(changes[1].delete?.item, "c")
296 | XCTAssertEqual(changes[1].delete?.index, 2)
297 | }
298 |
299 | func testReplaceInsertReplaceDelete() {
300 | let old = Array("1302")
301 | let new = Array("0231")
302 |
303 | let changes = diffWF(old: old, new: new)
304 | XCTAssertEqual(changes.count, 4)
305 |
306 | XCTAssertNotNil(changes[0].replace)
307 | XCTAssertNotNil(changes[1].insert)
308 | XCTAssertNotNil(changes[2].replace)
309 | XCTAssertNotNil(changes[3].delete)
310 | }
311 |
312 | func testReplaceMoveReplace() {
313 | let old = Array("2013")
314 | let new = Array("1302")
315 |
316 | let changes = diffWFReduceMove(old: old, new: new)
317 | XCTAssertEqual(changes.count, 3)
318 |
319 | XCTAssertNotNil(changes[0].replace)
320 | XCTAssertNotNil(changes[1].move)
321 | XCTAssertNotNil(changes[2].replace)
322 | }
323 | }
324 |
--------------------------------------------------------------------------------
/Example/Benchmark/Benchmark.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | AD52938D5D77D1530AF2B2E5 /* Pods_Benchmark.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F22F03CE6FDE502CC7191156 /* Pods_Benchmark.framework */; };
11 | D2FFBC191FFE2DDA0051D319 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FFBC181FFE2DDA0051D319 /* AppDelegate.swift */; };
12 | D2FFBC1B1FFE2DDA0051D319 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FFBC1A1FFE2DDA0051D319 /* ViewController.swift */; };
13 | D2FFBC1E1FFE2DDA0051D319 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D2FFBC1C1FFE2DDA0051D319 /* Main.storyboard */; };
14 | D2FFBC201FFE2DDA0051D319 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D2FFBC1F1FFE2DDA0051D319 /* Assets.xcassets */; };
15 | D2FFBC231FFE2DDA0051D319 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D2FFBC211FFE2DDA0051D319 /* LaunchScreen.storyboard */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXFileReference section */
19 | 3B03C67F0552DE2FBBFD1D75 /* Pods-Benchmark.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Benchmark.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Benchmark/Pods-Benchmark.debug.xcconfig"; sourceTree = ""; };
20 | D2FFBC151FFE2DDA0051D319 /* Benchmark.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Benchmark.app; sourceTree = BUILT_PRODUCTS_DIR; };
21 | D2FFBC181FFE2DDA0051D319 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
22 | D2FFBC1A1FFE2DDA0051D319 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
23 | D2FFBC1D1FFE2DDA0051D319 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
24 | D2FFBC1F1FFE2DDA0051D319 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
25 | D2FFBC221FFE2DDA0051D319 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
26 | D2FFBC241FFE2DDA0051D319 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
27 | E523C4F612B9058A8672DFFD /* Pods-Benchmark.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Benchmark.release.xcconfig"; path = "Pods/Target Support Files/Pods-Benchmark/Pods-Benchmark.release.xcconfig"; sourceTree = ""; };
28 | F22F03CE6FDE502CC7191156 /* Pods_Benchmark.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Benchmark.framework; sourceTree = BUILT_PRODUCTS_DIR; };
29 | /* End PBXFileReference section */
30 |
31 | /* Begin PBXFrameworksBuildPhase section */
32 | D2FFBC121FFE2DDA0051D319 /* Frameworks */ = {
33 | isa = PBXFrameworksBuildPhase;
34 | buildActionMask = 2147483647;
35 | files = (
36 | AD52938D5D77D1530AF2B2E5 /* Pods_Benchmark.framework in Frameworks */,
37 | );
38 | runOnlyForDeploymentPostprocessing = 0;
39 | };
40 | /* End PBXFrameworksBuildPhase section */
41 |
42 | /* Begin PBXGroup section */
43 | 340DDC15F5832ABE9389387C /* Pods */ = {
44 | isa = PBXGroup;
45 | children = (
46 | 3B03C67F0552DE2FBBFD1D75 /* Pods-Benchmark.debug.xcconfig */,
47 | E523C4F612B9058A8672DFFD /* Pods-Benchmark.release.xcconfig */,
48 | );
49 | name = Pods;
50 | sourceTree = "";
51 | };
52 | 38C4FDCAD1AC3914CCF0F9A5 /* Frameworks */ = {
53 | isa = PBXGroup;
54 | children = (
55 | F22F03CE6FDE502CC7191156 /* Pods_Benchmark.framework */,
56 | );
57 | name = Frameworks;
58 | sourceTree = "";
59 | };
60 | D2FFBC0C1FFE2DDA0051D319 = {
61 | isa = PBXGroup;
62 | children = (
63 | D2FFBC171FFE2DDA0051D319 /* Benchmark */,
64 | D2FFBC161FFE2DDA0051D319 /* Products */,
65 | 340DDC15F5832ABE9389387C /* Pods */,
66 | 38C4FDCAD1AC3914CCF0F9A5 /* Frameworks */,
67 | );
68 | sourceTree = "";
69 | };
70 | D2FFBC161FFE2DDA0051D319 /* Products */ = {
71 | isa = PBXGroup;
72 | children = (
73 | D2FFBC151FFE2DDA0051D319 /* Benchmark.app */,
74 | );
75 | name = Products;
76 | sourceTree = "";
77 | };
78 | D2FFBC171FFE2DDA0051D319 /* Benchmark */ = {
79 | isa = PBXGroup;
80 | children = (
81 | D2FFBC181FFE2DDA0051D319 /* AppDelegate.swift */,
82 | D2FFBC1A1FFE2DDA0051D319 /* ViewController.swift */,
83 | D2FFBC1C1FFE2DDA0051D319 /* Main.storyboard */,
84 | D2FFBC1F1FFE2DDA0051D319 /* Assets.xcassets */,
85 | D2FFBC211FFE2DDA0051D319 /* LaunchScreen.storyboard */,
86 | D2FFBC241FFE2DDA0051D319 /* Info.plist */,
87 | );
88 | path = Benchmark;
89 | sourceTree = "";
90 | };
91 | /* End PBXGroup section */
92 |
93 | /* Begin PBXNativeTarget section */
94 | D2FFBC141FFE2DDA0051D319 /* Benchmark */ = {
95 | isa = PBXNativeTarget;
96 | buildConfigurationList = D2FFBC271FFE2DDA0051D319 /* Build configuration list for PBXNativeTarget "Benchmark" */;
97 | buildPhases = (
98 | A8B48AD66A013BA7857FFFF5 /* [CP] Check Pods Manifest.lock */,
99 | D2FFBC111FFE2DDA0051D319 /* Sources */,
100 | D2FFBC121FFE2DDA0051D319 /* Frameworks */,
101 | D2FFBC131FFE2DDA0051D319 /* Resources */,
102 | 9B79B5A32C81AC1515A0C513 /* [CP] Embed Pods Frameworks */,
103 | );
104 | buildRules = (
105 | );
106 | dependencies = (
107 | );
108 | name = Benchmark;
109 | productName = Benchmark;
110 | productReference = D2FFBC151FFE2DDA0051D319 /* Benchmark.app */;
111 | productType = "com.apple.product-type.application";
112 | };
113 | /* End PBXNativeTarget section */
114 |
115 | /* Begin PBXProject section */
116 | D2FFBC0D1FFE2DDA0051D319 /* Project object */ = {
117 | isa = PBXProject;
118 | attributes = {
119 | LastSwiftUpdateCheck = 0920;
120 | LastUpgradeCheck = 1000;
121 | ORGANIZATIONNAME = Fantageek;
122 | TargetAttributes = {
123 | D2FFBC141FFE2DDA0051D319 = {
124 | CreatedOnToolsVersion = 9.2;
125 | ProvisioningStyle = Automatic;
126 | };
127 | };
128 | };
129 | buildConfigurationList = D2FFBC101FFE2DDA0051D319 /* Build configuration list for PBXProject "Benchmark" */;
130 | compatibilityVersion = "Xcode 8.0";
131 | developmentRegion = en;
132 | hasScannedForEncodings = 0;
133 | knownRegions = (
134 | en,
135 | Base,
136 | );
137 | mainGroup = D2FFBC0C1FFE2DDA0051D319;
138 | productRefGroup = D2FFBC161FFE2DDA0051D319 /* Products */;
139 | projectDirPath = "";
140 | projectRoot = "";
141 | targets = (
142 | D2FFBC141FFE2DDA0051D319 /* Benchmark */,
143 | );
144 | };
145 | /* End PBXProject section */
146 |
147 | /* Begin PBXResourcesBuildPhase section */
148 | D2FFBC131FFE2DDA0051D319 /* Resources */ = {
149 | isa = PBXResourcesBuildPhase;
150 | buildActionMask = 2147483647;
151 | files = (
152 | D2FFBC231FFE2DDA0051D319 /* LaunchScreen.storyboard in Resources */,
153 | D2FFBC201FFE2DDA0051D319 /* Assets.xcassets in Resources */,
154 | D2FFBC1E1FFE2DDA0051D319 /* Main.storyboard in Resources */,
155 | );
156 | runOnlyForDeploymentPostprocessing = 0;
157 | };
158 | /* End PBXResourcesBuildPhase section */
159 |
160 | /* Begin PBXShellScriptBuildPhase section */
161 | 9B79B5A32C81AC1515A0C513 /* [CP] Embed Pods Frameworks */ = {
162 | isa = PBXShellScriptBuildPhase;
163 | buildActionMask = 2147483647;
164 | files = (
165 | );
166 | inputPaths = (
167 | "${PODS_ROOT}/Target Support Files/Pods-Benchmark/Pods-Benchmark-frameworks.sh",
168 | "${BUILT_PRODUCTS_DIR}/Changeset/Changeset.framework",
169 | "${BUILT_PRODUCTS_DIR}/DeepDiff/DeepDiff.framework",
170 | "${BUILT_PRODUCTS_DIR}/Differ/Differ.framework",
171 | "${BUILT_PRODUCTS_DIR}/Dwifft/Dwifft.framework",
172 | "${BUILT_PRODUCTS_DIR}/ListDiff/ListDiff.framework",
173 | );
174 | name = "[CP] Embed Pods Frameworks";
175 | outputPaths = (
176 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Changeset.framework",
177 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DeepDiff.framework",
178 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Differ.framework",
179 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Dwifft.framework",
180 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ListDiff.framework",
181 | );
182 | runOnlyForDeploymentPostprocessing = 0;
183 | shellPath = /bin/sh;
184 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Benchmark/Pods-Benchmark-frameworks.sh\"\n";
185 | showEnvVarsInLog = 0;
186 | };
187 | A8B48AD66A013BA7857FFFF5 /* [CP] Check Pods Manifest.lock */ = {
188 | isa = PBXShellScriptBuildPhase;
189 | buildActionMask = 2147483647;
190 | files = (
191 | );
192 | inputPaths = (
193 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
194 | "${PODS_ROOT}/Manifest.lock",
195 | );
196 | name = "[CP] Check Pods Manifest.lock";
197 | outputPaths = (
198 | "$(DERIVED_FILE_DIR)/Pods-Benchmark-checkManifestLockResult.txt",
199 | );
200 | runOnlyForDeploymentPostprocessing = 0;
201 | shellPath = /bin/sh;
202 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
203 | showEnvVarsInLog = 0;
204 | };
205 | /* End PBXShellScriptBuildPhase section */
206 |
207 | /* Begin PBXSourcesBuildPhase section */
208 | D2FFBC111FFE2DDA0051D319 /* Sources */ = {
209 | isa = PBXSourcesBuildPhase;
210 | buildActionMask = 2147483647;
211 | files = (
212 | D2FFBC1B1FFE2DDA0051D319 /* ViewController.swift in Sources */,
213 | D2FFBC191FFE2DDA0051D319 /* AppDelegate.swift in Sources */,
214 | );
215 | runOnlyForDeploymentPostprocessing = 0;
216 | };
217 | /* End PBXSourcesBuildPhase section */
218 |
219 | /* Begin PBXVariantGroup section */
220 | D2FFBC1C1FFE2DDA0051D319 /* Main.storyboard */ = {
221 | isa = PBXVariantGroup;
222 | children = (
223 | D2FFBC1D1FFE2DDA0051D319 /* Base */,
224 | );
225 | name = Main.storyboard;
226 | sourceTree = "";
227 | };
228 | D2FFBC211FFE2DDA0051D319 /* LaunchScreen.storyboard */ = {
229 | isa = PBXVariantGroup;
230 | children = (
231 | D2FFBC221FFE2DDA0051D319 /* Base */,
232 | );
233 | name = LaunchScreen.storyboard;
234 | sourceTree = "";
235 | };
236 | /* End PBXVariantGroup section */
237 |
238 | /* Begin XCBuildConfiguration section */
239 | D2FFBC251FFE2DDA0051D319 /* Debug */ = {
240 | isa = XCBuildConfiguration;
241 | buildSettings = {
242 | ALWAYS_SEARCH_USER_PATHS = NO;
243 | CLANG_ANALYZER_NONNULL = YES;
244 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
245 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
246 | CLANG_CXX_LIBRARY = "libc++";
247 | CLANG_ENABLE_MODULES = YES;
248 | CLANG_ENABLE_OBJC_ARC = YES;
249 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
250 | CLANG_WARN_BOOL_CONVERSION = YES;
251 | CLANG_WARN_COMMA = YES;
252 | CLANG_WARN_CONSTANT_CONVERSION = YES;
253 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
254 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
255 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
256 | CLANG_WARN_EMPTY_BODY = YES;
257 | CLANG_WARN_ENUM_CONVERSION = YES;
258 | CLANG_WARN_INFINITE_RECURSION = YES;
259 | CLANG_WARN_INT_CONVERSION = YES;
260 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
261 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
262 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
263 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
264 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
265 | CLANG_WARN_STRICT_PROTOTYPES = YES;
266 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
267 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
268 | CLANG_WARN_UNREACHABLE_CODE = YES;
269 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
270 | CODE_SIGN_IDENTITY = "iPhone Developer";
271 | COPY_PHASE_STRIP = NO;
272 | DEBUG_INFORMATION_FORMAT = dwarf;
273 | ENABLE_STRICT_OBJC_MSGSEND = YES;
274 | ENABLE_TESTABILITY = YES;
275 | GCC_C_LANGUAGE_STANDARD = gnu11;
276 | GCC_DYNAMIC_NO_PIC = NO;
277 | GCC_NO_COMMON_BLOCKS = YES;
278 | GCC_OPTIMIZATION_LEVEL = 0;
279 | GCC_PREPROCESSOR_DEFINITIONS = (
280 | "DEBUG=1",
281 | "$(inherited)",
282 | );
283 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
284 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
285 | GCC_WARN_UNDECLARED_SELECTOR = YES;
286 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
287 | GCC_WARN_UNUSED_FUNCTION = YES;
288 | GCC_WARN_UNUSED_VARIABLE = YES;
289 | IPHONEOS_DEPLOYMENT_TARGET = 11.2;
290 | MTL_ENABLE_DEBUG_INFO = YES;
291 | ONLY_ACTIVE_ARCH = YES;
292 | SDKROOT = iphoneos;
293 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
294 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
295 | SWIFT_VERSION = 5.0;
296 | };
297 | name = Debug;
298 | };
299 | D2FFBC261FFE2DDA0051D319 /* Release */ = {
300 | isa = XCBuildConfiguration;
301 | buildSettings = {
302 | ALWAYS_SEARCH_USER_PATHS = NO;
303 | CLANG_ANALYZER_NONNULL = YES;
304 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
305 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
306 | CLANG_CXX_LIBRARY = "libc++";
307 | CLANG_ENABLE_MODULES = YES;
308 | CLANG_ENABLE_OBJC_ARC = YES;
309 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
310 | CLANG_WARN_BOOL_CONVERSION = YES;
311 | CLANG_WARN_COMMA = YES;
312 | CLANG_WARN_CONSTANT_CONVERSION = YES;
313 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
314 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
315 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
316 | CLANG_WARN_EMPTY_BODY = YES;
317 | CLANG_WARN_ENUM_CONVERSION = YES;
318 | CLANG_WARN_INFINITE_RECURSION = YES;
319 | CLANG_WARN_INT_CONVERSION = YES;
320 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
321 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
322 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
323 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
324 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
325 | CLANG_WARN_STRICT_PROTOTYPES = YES;
326 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
327 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
328 | CLANG_WARN_UNREACHABLE_CODE = YES;
329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
330 | CODE_SIGN_IDENTITY = "iPhone Developer";
331 | COPY_PHASE_STRIP = NO;
332 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
333 | ENABLE_NS_ASSERTIONS = NO;
334 | ENABLE_STRICT_OBJC_MSGSEND = YES;
335 | GCC_C_LANGUAGE_STANDARD = gnu11;
336 | GCC_NO_COMMON_BLOCKS = YES;
337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
339 | GCC_WARN_UNDECLARED_SELECTOR = YES;
340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
341 | GCC_WARN_UNUSED_FUNCTION = YES;
342 | GCC_WARN_UNUSED_VARIABLE = YES;
343 | IPHONEOS_DEPLOYMENT_TARGET = 11.2;
344 | MTL_ENABLE_DEBUG_INFO = NO;
345 | SDKROOT = iphoneos;
346 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
347 | SWIFT_VERSION = 5.0;
348 | VALIDATE_PRODUCT = YES;
349 | };
350 | name = Release;
351 | };
352 | D2FFBC281FFE2DDA0051D319 /* Debug */ = {
353 | isa = XCBuildConfiguration;
354 | baseConfigurationReference = 3B03C67F0552DE2FBBFD1D75 /* Pods-Benchmark.debug.xcconfig */;
355 | buildSettings = {
356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
357 | CODE_SIGN_STYLE = Automatic;
358 | DEVELOPMENT_TEAM = V43WB5JL68;
359 | INFOPLIST_FILE = Benchmark/Info.plist;
360 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
361 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
362 | PRODUCT_BUNDLE_IDENTIFIER = com.onmyway133.Benchmark;
363 | PRODUCT_NAME = "$(TARGET_NAME)";
364 | TARGETED_DEVICE_FAMILY = "1,2";
365 | };
366 | name = Debug;
367 | };
368 | D2FFBC291FFE2DDA0051D319 /* Release */ = {
369 | isa = XCBuildConfiguration;
370 | baseConfigurationReference = E523C4F612B9058A8672DFFD /* Pods-Benchmark.release.xcconfig */;
371 | buildSettings = {
372 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
373 | CODE_SIGN_STYLE = Automatic;
374 | DEVELOPMENT_TEAM = V43WB5JL68;
375 | INFOPLIST_FILE = Benchmark/Info.plist;
376 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
377 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
378 | PRODUCT_BUNDLE_IDENTIFIER = com.onmyway133.Benchmark;
379 | PRODUCT_NAME = "$(TARGET_NAME)";
380 | TARGETED_DEVICE_FAMILY = "1,2";
381 | };
382 | name = Release;
383 | };
384 | /* End XCBuildConfiguration section */
385 |
386 | /* Begin XCConfigurationList section */
387 | D2FFBC101FFE2DDA0051D319 /* Build configuration list for PBXProject "Benchmark" */ = {
388 | isa = XCConfigurationList;
389 | buildConfigurations = (
390 | D2FFBC251FFE2DDA0051D319 /* Debug */,
391 | D2FFBC261FFE2DDA0051D319 /* Release */,
392 | );
393 | defaultConfigurationIsVisible = 0;
394 | defaultConfigurationName = Release;
395 | };
396 | D2FFBC271FFE2DDA0051D319 /* Build configuration list for PBXNativeTarget "Benchmark" */ = {
397 | isa = XCConfigurationList;
398 | buildConfigurations = (
399 | D2FFBC281FFE2DDA0051D319 /* Debug */,
400 | D2FFBC291FFE2DDA0051D319 /* Release */,
401 | );
402 | defaultConfigurationIsVisible = 0;
403 | defaultConfigurationName = Release;
404 | };
405 | /* End XCConfigurationList section */
406 | };
407 | rootObject = D2FFBC0D1FFE2DDA0051D319 /* Project object */;
408 | }
409 |
--------------------------------------------------------------------------------
/Example/Benchmark/Benchmark.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/Benchmark/Benchmark.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/Benchmark/Benchmark.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/Benchmark/Benchmark/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Benchmark
4 | //
5 | // Created by Khoa Pham on 04.01.2018.
6 | // Copyright © 2018 Fantageek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | internal func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/Example/Benchmark/Benchmark/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | }
88 | ],
89 | "info" : {
90 | "version" : 1,
91 | "author" : "xcode"
92 | }
93 | }
--------------------------------------------------------------------------------
/Example/Benchmark/Benchmark/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Example/Benchmark/Benchmark/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Example/Benchmark/Benchmark/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Example/Benchmark/Benchmark/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Benchmark
4 | //
5 | // Created by Khoa Pham on 04.01.2018.
6 | // Copyright © 2018 Fantageek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import DeepDiff
11 | import Dwifft
12 | import Changeset
13 | import Differ
14 | import ListDiff
15 |
16 | class ViewController: UIViewController {
17 |
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 | }
21 |
22 | override func viewWillAppear(_ animated: Bool) {
23 | super.viewWillAppear(animated)
24 |
25 | benchmarkManyFrameworks()
26 | // benchmarkSelf()
27 | }
28 |
29 | private func benchmarkManyFrameworks() {
30 | let (old, new) = generate(count: 2000, removeRange: 100..<200, addRange: 1000..<1200)
31 |
32 | benchmark(name: "DeepDiff", closure: {
33 | _ = DeepDiff.diff(old: old, new: new)
34 | })
35 |
36 | benchmark(name: "Differ", closure: {
37 | _ = old.diffTraces(to: new)
38 | })
39 |
40 | benchmark(name: "Dwifft", closure: {
41 | _ = Dwifft.diff(old, new)
42 | })
43 |
44 | benchmark(name: "Changeset", closure: {
45 | _ = Changeset.edits(from: old, to: new)
46 | })
47 |
48 | benchmark(name: "ListDiff", closure: {
49 | _ = ListDiff.List.diffing(oldArray: old, newArray: new)
50 | })
51 | }
52 |
53 | private func benchmarkSelf() {
54 | benchmark(name: "10000", closure: {
55 | let (old, new) = generate(count: 10000, removeRange: 1000..<2000, addRange: 5000..<7000)
56 | _ = DeepDiff.diff(old: old, new: new)
57 | })
58 |
59 | benchmark(name: "20000", closure: {
60 | let (old, new) = generate(count: 20000, removeRange: 2000..<4000, addRange: 10000..<14000)
61 | _ = DeepDiff.diff(old: old, new: new)
62 | })
63 |
64 | benchmark(name: "50000", closure: {
65 | let (old, new) = generate(count: 50000, removeRange: 5000..<10000, addRange: 10000..<15000)
66 | _ = DeepDiff.diff(old: old, new: new)
67 | })
68 | }
69 |
70 | private func benchmark(name: String ,closure: () -> Void) {
71 | let start = Date()
72 | closure()
73 | let end = Date()
74 |
75 | print("\(name): \(end.timeIntervalSince1970 - start.timeIntervalSince1970)s")
76 | }
77 |
78 | // Generate old and new
79 | func generate(
80 | count: Int,
81 | removeRange: Range? = nil,
82 | addRange: Range? = nil)
83 | -> (old: Array, new: Array) {
84 |
85 | let old = Array(repeating: UUID().uuidString, count: count)
86 | var new = old
87 |
88 | if let removeRange = removeRange {
89 | new.removeSubrange(removeRange)
90 | }
91 |
92 | if let addRange = addRange {
93 | new.insert(
94 | contentsOf: Array(repeating: UUID().uuidString, count: addRange.count),
95 | at: addRange.lowerBound
96 | )
97 | }
98 |
99 | return (old: old, new: new)
100 | }
101 | }
102 |
103 | extension String: ListDiff.Diffable {
104 | public var diffIdentifier: AnyHashable {
105 | return self
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/Example/Benchmark/Podfile:
--------------------------------------------------------------------------------
1 | use_frameworks!
2 |
3 | platform :ios, '9.0'
4 |
5 | target 'Benchmark' do
6 | pod 'DeepDiff', path: '../../'
7 | pod 'Dwifft'
8 | pod 'Changeset'
9 | pod 'Differ'
10 | pod 'ListDiff'
11 | end
12 |
13 |
--------------------------------------------------------------------------------
/Example/Benchmark/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Changeset (3.0)
3 | - DeepDiff (2.2.0)
4 | - Differ (1.0.3)
5 | - Dwifft (0.8)
6 | - ListDiff (0.1.0)
7 |
8 | DEPENDENCIES:
9 | - Changeset
10 | - DeepDiff (from `../../`)
11 | - Differ
12 | - Dwifft
13 | - ListDiff
14 |
15 | SPEC REPOS:
16 | https://github.com/cocoapods/specs.git:
17 | - Changeset
18 | - Differ
19 | - Dwifft
20 | - ListDiff
21 |
22 | EXTERNAL SOURCES:
23 | DeepDiff:
24 | :path: "../../"
25 |
26 | SPEC CHECKSUMS:
27 | Changeset: cbb84a74639049a7b96fc64851e8a9cfcc3c9989
28 | DeepDiff: e329bc46dd14ca788d8ec08d34420799ba1d77f2
29 | Differ: 0c220ac75542f5f17f91b1303b85bf07d871ecd7
30 | Dwifft: 463e61d07322fdf83b32ee6975ca1b20708db746
31 | ListDiff: 8a29c2ae3c8370cc989b6d6d50bb40d58c4e9ede
32 |
33 | PODFILE CHECKSUM: 59723220655512645b381c37f63e9627012758a1
34 |
35 | COCOAPODS: 1.7.0.beta.3
36 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | D23B9F441FA92867008363A1 /* CollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23B9F431FA92867008363A1 /* CollectionViewCell.swift */; };
11 | D23B9F461FA92ACB008363A1 /* Collection+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23B9F451FA92ACB008363A1 /* Collection+Extensions.swift */; };
12 | D23B9F481FA9315F008363A1 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23B9F471FA9315F008363A1 /* TableViewController.swift */; };
13 | D23B9F4A1FA93199008363A1 /* TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23B9F491FA93199008363A1 /* TableViewCell.swift */; };
14 | D26B2DF121DF8917000628A0 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26B2DF021DF8917000628A0 /* Color+Extensions.swift */; };
15 | D2E33C861FAB4DC000BCE1E2 /* DataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E33C851FAB4DC000BCE1E2 /* DataSet.swift */; };
16 | D5C7F74E1C3BC9CE008CDDBA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D5C7F74C1C3BC9CE008CDDBA /* LaunchScreen.storyboard */; };
17 | D5C7F75B1C3BCA1E008CDDBA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D5C7F7571C3BCA1E008CDDBA /* Assets.xcassets */; };
18 | D5C7F75C1C3BCA1E008CDDBA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C7F7591C3BCA1E008CDDBA /* AppDelegate.swift */; };
19 | D5C7F75D1C3BCA1E008CDDBA /* CollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C7F75A1C3BCA1E008CDDBA /* CollectionViewController.swift */; };
20 | E6843D82FA3B5B3C9561C7A7 /* Pods_DeepDiffDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5545A1921B324B7D402E3AB6 /* Pods_DeepDiffDemo.framework */; };
21 | /* End PBXBuildFile section */
22 |
23 | /* Begin PBXFileReference section */
24 | 312A6379467401DAF6A566F0 /* Pods-DeepDiffDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DeepDiffDemo.debug.xcconfig"; path = "Target Support Files/Pods-DeepDiffDemo/Pods-DeepDiffDemo.debug.xcconfig"; sourceTree = ""; };
25 | 5545A1921B324B7D402E3AB6 /* Pods_DeepDiffDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DeepDiffDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; };
26 | 9381C3F3F924D4469B231A2E /* Pods-DeepDiffDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DeepDiffDemo.release.xcconfig"; path = "Target Support Files/Pods-DeepDiffDemo/Pods-DeepDiffDemo.release.xcconfig"; sourceTree = ""; };
27 | D23B9F431FA92867008363A1 /* CollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewCell.swift; sourceTree = ""; };
28 | D23B9F451FA92ACB008363A1 /* Collection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+Extensions.swift"; sourceTree = ""; };
29 | D23B9F471FA9315F008363A1 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; };
30 | D23B9F491FA93199008363A1 /* TableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCell.swift; sourceTree = ""; };
31 | D26B2DF021DF8917000628A0 /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = ""; };
32 | D2A8136D1FABA4D000A94BDE /* ExceptionCatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExceptionCatcher.h; sourceTree = ""; };
33 | D2A8136E1FABA51100A94BDE /* DeepDiffDemo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DeepDiffDemo-Bridging-Header.h"; sourceTree = ""; };
34 | D2E33C851FAB4DC000BCE1E2 /* DataSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSet.swift; sourceTree = ""; };
35 | D5C7F7401C3BC9CE008CDDBA /* DeepDiffDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DeepDiffDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
36 | D5C7F74D1C3BC9CE008CDDBA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
37 | D5C7F74F1C3BC9CE008CDDBA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
38 | D5C7F7571C3BCA1E008CDDBA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
39 | D5C7F7591C3BCA1E008CDDBA /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; tabWidth = 2; };
40 | D5C7F75A1C3BCA1E008CDDBA /* CollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewController.swift; sourceTree = ""; };
41 | /* End PBXFileReference section */
42 |
43 | /* Begin PBXFrameworksBuildPhase section */
44 | D5C7F73D1C3BC9CE008CDDBA /* Frameworks */ = {
45 | isa = PBXFrameworksBuildPhase;
46 | buildActionMask = 2147483647;
47 | files = (
48 | E6843D82FA3B5B3C9561C7A7 /* Pods_DeepDiffDemo.framework in Frameworks */,
49 | );
50 | runOnlyForDeploymentPostprocessing = 0;
51 | };
52 | /* End PBXFrameworksBuildPhase section */
53 |
54 | /* Begin PBXGroup section */
55 | 7BCE04D5A9C7F9E9AFFB6967 /* Frameworks */ = {
56 | isa = PBXGroup;
57 | children = (
58 | 5545A1921B324B7D402E3AB6 /* Pods_DeepDiffDemo.framework */,
59 | );
60 | name = Frameworks;
61 | sourceTree = "";
62 | };
63 | A26320D0FD94FE94FDF8EA48 /* Pods */ = {
64 | isa = PBXGroup;
65 | children = (
66 | 312A6379467401DAF6A566F0 /* Pods-DeepDiffDemo.debug.xcconfig */,
67 | 9381C3F3F924D4469B231A2E /* Pods-DeepDiffDemo.release.xcconfig */,
68 | );
69 | path = Pods;
70 | sourceTree = "";
71 | };
72 | D5C7F7371C3BC9CE008CDDBA = {
73 | isa = PBXGroup;
74 | children = (
75 | D5C7F7421C3BC9CE008CDDBA /* DeepDiffDemo */,
76 | D5C7F7411C3BC9CE008CDDBA /* Products */,
77 | A26320D0FD94FE94FDF8EA48 /* Pods */,
78 | 7BCE04D5A9C7F9E9AFFB6967 /* Frameworks */,
79 | );
80 | sourceTree = "";
81 | };
82 | D5C7F7411C3BC9CE008CDDBA /* Products */ = {
83 | isa = PBXGroup;
84 | children = (
85 | D5C7F7401C3BC9CE008CDDBA /* DeepDiffDemo.app */,
86 | );
87 | name = Products;
88 | sourceTree = "";
89 | };
90 | D5C7F7421C3BC9CE008CDDBA /* DeepDiffDemo */ = {
91 | isa = PBXGroup;
92 | children = (
93 | D5C7F7561C3BCA1E008CDDBA /* Resources */,
94 | D5C7F7581C3BCA1E008CDDBA /* Sources */,
95 | D5C7F7551C3BC9EA008CDDBA /* Supporting Files */,
96 | );
97 | path = DeepDiffDemo;
98 | sourceTree = "";
99 | };
100 | D5C7F7551C3BC9EA008CDDBA /* Supporting Files */ = {
101 | isa = PBXGroup;
102 | children = (
103 | D5C7F74C1C3BC9CE008CDDBA /* LaunchScreen.storyboard */,
104 | D5C7F74F1C3BC9CE008CDDBA /* Info.plist */,
105 | );
106 | name = "Supporting Files";
107 | sourceTree = "";
108 | };
109 | D5C7F7561C3BCA1E008CDDBA /* Resources */ = {
110 | isa = PBXGroup;
111 | children = (
112 | D5C7F7571C3BCA1E008CDDBA /* Assets.xcassets */,
113 | );
114 | path = Resources;
115 | sourceTree = "";
116 | };
117 | D5C7F7581C3BCA1E008CDDBA /* Sources */ = {
118 | isa = PBXGroup;
119 | children = (
120 | D5C7F7591C3BCA1E008CDDBA /* AppDelegate.swift */,
121 | D5C7F75A1C3BCA1E008CDDBA /* CollectionViewController.swift */,
122 | D23B9F431FA92867008363A1 /* CollectionViewCell.swift */,
123 | D23B9F451FA92ACB008363A1 /* Collection+Extensions.swift */,
124 | D23B9F471FA9315F008363A1 /* TableViewController.swift */,
125 | D23B9F491FA93199008363A1 /* TableViewCell.swift */,
126 | D2E33C851FAB4DC000BCE1E2 /* DataSet.swift */,
127 | D2A8136D1FABA4D000A94BDE /* ExceptionCatcher.h */,
128 | D2A8136E1FABA51100A94BDE /* DeepDiffDemo-Bridging-Header.h */,
129 | D26B2DF021DF8917000628A0 /* Color+Extensions.swift */,
130 | );
131 | path = Sources;
132 | sourceTree = "";
133 | };
134 | /* End PBXGroup section */
135 |
136 | /* Begin PBXNativeTarget section */
137 | D5C7F73F1C3BC9CE008CDDBA /* DeepDiffDemo */ = {
138 | isa = PBXNativeTarget;
139 | buildConfigurationList = D5C7F7521C3BC9CE008CDDBA /* Build configuration list for PBXNativeTarget "DeepDiffDemo" */;
140 | buildPhases = (
141 | FF987F507328544E7F0C917E /* [CP] Check Pods Manifest.lock */,
142 | D5C7F73C1C3BC9CE008CDDBA /* Sources */,
143 | D5C7F73D1C3BC9CE008CDDBA /* Frameworks */,
144 | D5C7F73E1C3BC9CE008CDDBA /* Resources */,
145 | 0F65FCA70DF2D90D4E9B8953 /* [CP] Embed Pods Frameworks */,
146 | );
147 | buildRules = (
148 | );
149 | dependencies = (
150 | );
151 | name = DeepDiffDemo;
152 | productName = DeepDiffDemo;
153 | productReference = D5C7F7401C3BC9CE008CDDBA /* DeepDiffDemo.app */;
154 | productType = "com.apple.product-type.application";
155 | };
156 | /* End PBXNativeTarget section */
157 |
158 | /* Begin PBXProject section */
159 | D5C7F7381C3BC9CE008CDDBA /* Project object */ = {
160 | isa = PBXProject;
161 | attributes = {
162 | LastSwiftUpdateCheck = 0720;
163 | LastUpgradeCheck = 1020;
164 | ORGANIZATIONNAME = "Hyper Interaktiv AS";
165 | TargetAttributes = {
166 | D5C7F73F1C3BC9CE008CDDBA = {
167 | CreatedOnToolsVersion = 7.2;
168 | DevelopmentTeam = T78DK947F2;
169 | LastSwiftMigration = 0800;
170 | };
171 | };
172 | };
173 | buildConfigurationList = D5C7F73B1C3BC9CE008CDDBA /* Build configuration list for PBXProject "DeepDiffDemo" */;
174 | compatibilityVersion = "Xcode 3.2";
175 | developmentRegion = en;
176 | hasScannedForEncodings = 0;
177 | knownRegions = (
178 | en,
179 | Base,
180 | );
181 | mainGroup = D5C7F7371C3BC9CE008CDDBA;
182 | productRefGroup = D5C7F7411C3BC9CE008CDDBA /* Products */;
183 | projectDirPath = "";
184 | projectRoot = "";
185 | targets = (
186 | D5C7F73F1C3BC9CE008CDDBA /* DeepDiffDemo */,
187 | );
188 | };
189 | /* End PBXProject section */
190 |
191 | /* Begin PBXResourcesBuildPhase section */
192 | D5C7F73E1C3BC9CE008CDDBA /* Resources */ = {
193 | isa = PBXResourcesBuildPhase;
194 | buildActionMask = 2147483647;
195 | files = (
196 | D5C7F75B1C3BCA1E008CDDBA /* Assets.xcassets in Resources */,
197 | D5C7F74E1C3BC9CE008CDDBA /* LaunchScreen.storyboard in Resources */,
198 | );
199 | runOnlyForDeploymentPostprocessing = 0;
200 | };
201 | /* End PBXResourcesBuildPhase section */
202 |
203 | /* Begin PBXShellScriptBuildPhase section */
204 | 0F65FCA70DF2D90D4E9B8953 /* [CP] Embed Pods Frameworks */ = {
205 | isa = PBXShellScriptBuildPhase;
206 | buildActionMask = 2147483647;
207 | files = (
208 | );
209 | inputPaths = (
210 | "${PODS_ROOT}/Target Support Files/Pods-DeepDiffDemo/Pods-DeepDiffDemo-frameworks.sh",
211 | "${BUILT_PRODUCTS_DIR}/Anchors/Anchors.framework",
212 | "${BUILT_PRODUCTS_DIR}/DeepDiff/DeepDiff.framework",
213 | );
214 | name = "[CP] Embed Pods Frameworks";
215 | outputPaths = (
216 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Anchors.framework",
217 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DeepDiff.framework",
218 | );
219 | runOnlyForDeploymentPostprocessing = 0;
220 | shellPath = /bin/sh;
221 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-DeepDiffDemo/Pods-DeepDiffDemo-frameworks.sh\"\n";
222 | showEnvVarsInLog = 0;
223 | };
224 | FF987F507328544E7F0C917E /* [CP] Check Pods Manifest.lock */ = {
225 | isa = PBXShellScriptBuildPhase;
226 | buildActionMask = 2147483647;
227 | files = (
228 | );
229 | inputFileListPaths = (
230 | );
231 | inputPaths = (
232 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
233 | "${PODS_ROOT}/Manifest.lock",
234 | );
235 | name = "[CP] Check Pods Manifest.lock";
236 | outputFileListPaths = (
237 | );
238 | outputPaths = (
239 | "$(DERIVED_FILE_DIR)/Pods-DeepDiffDemo-checkManifestLockResult.txt",
240 | );
241 | runOnlyForDeploymentPostprocessing = 0;
242 | shellPath = /bin/sh;
243 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
244 | showEnvVarsInLog = 0;
245 | };
246 | /* End PBXShellScriptBuildPhase section */
247 |
248 | /* Begin PBXSourcesBuildPhase section */
249 | D5C7F73C1C3BC9CE008CDDBA /* Sources */ = {
250 | isa = PBXSourcesBuildPhase;
251 | buildActionMask = 2147483647;
252 | files = (
253 | D5C7F75D1C3BCA1E008CDDBA /* CollectionViewController.swift in Sources */,
254 | D23B9F461FA92ACB008363A1 /* Collection+Extensions.swift in Sources */,
255 | D5C7F75C1C3BCA1E008CDDBA /* AppDelegate.swift in Sources */,
256 | D23B9F4A1FA93199008363A1 /* TableViewCell.swift in Sources */,
257 | D23B9F481FA9315F008363A1 /* TableViewController.swift in Sources */,
258 | D2E33C861FAB4DC000BCE1E2 /* DataSet.swift in Sources */,
259 | D23B9F441FA92867008363A1 /* CollectionViewCell.swift in Sources */,
260 | D26B2DF121DF8917000628A0 /* Color+Extensions.swift in Sources */,
261 | );
262 | runOnlyForDeploymentPostprocessing = 0;
263 | };
264 | /* End PBXSourcesBuildPhase section */
265 |
266 | /* Begin PBXVariantGroup section */
267 | D5C7F74C1C3BC9CE008CDDBA /* LaunchScreen.storyboard */ = {
268 | isa = PBXVariantGroup;
269 | children = (
270 | D5C7F74D1C3BC9CE008CDDBA /* Base */,
271 | );
272 | name = LaunchScreen.storyboard;
273 | sourceTree = "";
274 | };
275 | /* End PBXVariantGroup section */
276 |
277 | /* Begin XCBuildConfiguration section */
278 | D5C7F7501C3BC9CE008CDDBA /* Debug */ = {
279 | isa = XCBuildConfiguration;
280 | buildSettings = {
281 | ALWAYS_SEARCH_USER_PATHS = NO;
282 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
283 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
284 | CLANG_CXX_LIBRARY = "libc++";
285 | CLANG_ENABLE_MODULES = YES;
286 | CLANG_ENABLE_OBJC_ARC = YES;
287 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
288 | CLANG_WARN_BOOL_CONVERSION = YES;
289 | CLANG_WARN_COMMA = YES;
290 | CLANG_WARN_CONSTANT_CONVERSION = YES;
291 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
292 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
293 | CLANG_WARN_EMPTY_BODY = YES;
294 | CLANG_WARN_ENUM_CONVERSION = YES;
295 | CLANG_WARN_INFINITE_RECURSION = YES;
296 | CLANG_WARN_INT_CONVERSION = YES;
297 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
298 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
299 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
300 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
301 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
302 | CLANG_WARN_STRICT_PROTOTYPES = YES;
303 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
304 | CLANG_WARN_UNREACHABLE_CODE = YES;
305 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
306 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
307 | COPY_PHASE_STRIP = NO;
308 | DEBUG_INFORMATION_FORMAT = dwarf;
309 | ENABLE_STRICT_OBJC_MSGSEND = YES;
310 | ENABLE_TESTABILITY = YES;
311 | GCC_C_LANGUAGE_STANDARD = gnu99;
312 | GCC_DYNAMIC_NO_PIC = NO;
313 | GCC_NO_COMMON_BLOCKS = YES;
314 | GCC_OPTIMIZATION_LEVEL = 0;
315 | GCC_PREPROCESSOR_DEFINITIONS = (
316 | "DEBUG=1",
317 | "$(inherited)",
318 | );
319 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
320 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
321 | GCC_WARN_UNDECLARED_SELECTOR = YES;
322 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
323 | GCC_WARN_UNUSED_FUNCTION = YES;
324 | GCC_WARN_UNUSED_VARIABLE = YES;
325 | IPHONEOS_DEPLOYMENT_TARGET = 9.2;
326 | MTL_ENABLE_DEBUG_INFO = YES;
327 | ONLY_ACTIVE_ARCH = YES;
328 | SDKROOT = iphoneos;
329 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
330 | SWIFT_VERSION = 5.0;
331 | };
332 | name = Debug;
333 | };
334 | D5C7F7511C3BC9CE008CDDBA /* Release */ = {
335 | isa = XCBuildConfiguration;
336 | buildSettings = {
337 | ALWAYS_SEARCH_USER_PATHS = NO;
338 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
339 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
340 | CLANG_CXX_LIBRARY = "libc++";
341 | CLANG_ENABLE_MODULES = YES;
342 | CLANG_ENABLE_OBJC_ARC = YES;
343 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
344 | CLANG_WARN_BOOL_CONVERSION = YES;
345 | CLANG_WARN_COMMA = YES;
346 | CLANG_WARN_CONSTANT_CONVERSION = YES;
347 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
348 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
349 | CLANG_WARN_EMPTY_BODY = YES;
350 | CLANG_WARN_ENUM_CONVERSION = YES;
351 | CLANG_WARN_INFINITE_RECURSION = YES;
352 | CLANG_WARN_INT_CONVERSION = YES;
353 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
354 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
355 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
356 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
357 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
358 | CLANG_WARN_STRICT_PROTOTYPES = YES;
359 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
360 | CLANG_WARN_UNREACHABLE_CODE = YES;
361 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
362 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
363 | COPY_PHASE_STRIP = NO;
364 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
365 | ENABLE_NS_ASSERTIONS = NO;
366 | ENABLE_STRICT_OBJC_MSGSEND = YES;
367 | GCC_C_LANGUAGE_STANDARD = gnu99;
368 | GCC_NO_COMMON_BLOCKS = YES;
369 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
370 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
371 | GCC_WARN_UNDECLARED_SELECTOR = YES;
372 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
373 | GCC_WARN_UNUSED_FUNCTION = YES;
374 | GCC_WARN_UNUSED_VARIABLE = YES;
375 | IPHONEOS_DEPLOYMENT_TARGET = 9.2;
376 | MTL_ENABLE_DEBUG_INFO = NO;
377 | SDKROOT = iphoneos;
378 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
379 | SWIFT_VERSION = 5.0;
380 | VALIDATE_PRODUCT = YES;
381 | };
382 | name = Release;
383 | };
384 | D5C7F7531C3BC9CE008CDDBA /* Debug */ = {
385 | isa = XCBuildConfiguration;
386 | baseConfigurationReference = 312A6379467401DAF6A566F0 /* Pods-DeepDiffDemo.debug.xcconfig */;
387 | buildSettings = {
388 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
389 | DEVELOPMENT_TEAM = T78DK947F2;
390 | INFOPLIST_FILE = DeepDiffDemo/Info.plist;
391 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
392 | PRODUCT_BUNDLE_IDENTIFIER = com.fantageek.DeepDiffDemo;
393 | PRODUCT_NAME = "$(TARGET_NAME)";
394 | SWIFT_OBJC_BRIDGING_HEADER = "DeepDiffDemo/Sources/DeepDiffDemo-Bridging-Header.h";
395 | };
396 | name = Debug;
397 | };
398 | D5C7F7541C3BC9CE008CDDBA /* Release */ = {
399 | isa = XCBuildConfiguration;
400 | baseConfigurationReference = 9381C3F3F924D4469B231A2E /* Pods-DeepDiffDemo.release.xcconfig */;
401 | buildSettings = {
402 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
403 | DEVELOPMENT_TEAM = T78DK947F2;
404 | INFOPLIST_FILE = DeepDiffDemo/Info.plist;
405 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
406 | PRODUCT_BUNDLE_IDENTIFIER = com.fantageek.DeepDiffDemo;
407 | PRODUCT_NAME = "$(TARGET_NAME)";
408 | SWIFT_OBJC_BRIDGING_HEADER = "DeepDiffDemo/Sources/DeepDiffDemo-Bridging-Header.h";
409 | };
410 | name = Release;
411 | };
412 | /* End XCBuildConfiguration section */
413 |
414 | /* Begin XCConfigurationList section */
415 | D5C7F73B1C3BC9CE008CDDBA /* Build configuration list for PBXProject "DeepDiffDemo" */ = {
416 | isa = XCConfigurationList;
417 | buildConfigurations = (
418 | D5C7F7501C3BC9CE008CDDBA /* Debug */,
419 | D5C7F7511C3BC9CE008CDDBA /* Release */,
420 | );
421 | defaultConfigurationIsVisible = 0;
422 | defaultConfigurationName = Release;
423 | };
424 | D5C7F7521C3BC9CE008CDDBA /* Build configuration list for PBXNativeTarget "DeepDiffDemo" */ = {
425 | isa = XCConfigurationList;
426 | buildConfigurations = (
427 | D5C7F7531C3BC9CE008CDDBA /* Debug */,
428 | D5C7F7541C3BC9CE008CDDBA /* Release */,
429 | );
430 | defaultConfigurationIsVisible = 0;
431 | defaultConfigurationName = Release;
432 | };
433 | /* End XCConfigurationList section */
434 | };
435 | rootObject = D5C7F7381C3BC9CE008CDDBA /* Project object */;
436 | }
437 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo.xcodeproj/xcshareddata/xcschemes/DeepDiffDemo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
69 |
70 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "size" : "1024x1024",
46 | "scale" : "1x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Resources/Assets.xcassets/collection.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "grid.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Resources/Assets.xcassets/collection.imageset/grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/DeepDiff/2639b3a11054d666a4c5ec1b7dd2808e437f3289/Example/DeepDiffDemo/DeepDiffDemo/Resources/Assets.xcassets/collection.imageset/grid.png
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Resources/Assets.xcassets/table.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "list.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Resources/Assets.xcassets/table.imageset/list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/DeepDiff/2639b3a11054d666a4c5ec1b7dd2808e437f3289/Example/DeepDiffDemo/DeepDiffDemo/Resources/Assets.xcassets/table.imageset/list.png
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Sources/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import DeepDiff
3 |
4 | @UIApplicationMain
5 | class AppDelegate: UIResponder, UIApplicationDelegate {
6 |
7 | var window: UIWindow?
8 |
9 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
10 | window = UIWindow(frame: UIScreen.main.bounds)
11 |
12 | let tableController = TableViewController()
13 | tableController.tabBarItem.image = UIImage(named: "table")
14 | tableController.title = "UITableView"
15 |
16 | let collectionController = CollectionViewController()
17 | collectionController.tabBarItem.image = UIImage(named: "collection")
18 | collectionController.title = "UICollectionView"
19 |
20 | let tabController = UITabBarController()
21 |
22 | tabController.viewControllers = [
23 | UINavigationController(rootViewController: tableController),
24 | UINavigationController(rootViewController: collectionController)
25 | ]
26 |
27 | UINavigationBar.appearance().barTintColor = UIColor(hex: "#2ecc71")
28 |
29 | window?.rootViewController = tabController
30 | window?.makeKeyAndVisible()
31 |
32 | return true
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Sources/Collection+Extensions.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension MutableCollection {
4 | /// Shuffles the contents of this collection.
5 | mutating func shuffle() {
6 | let c = count
7 | guard c > 1 else { return }
8 |
9 | for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
10 | let d: Int = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
11 | let i = index(firstUnshuffled, offsetBy: d)
12 | swapAt(firstUnshuffled, i)
13 | }
14 | }
15 | }
16 |
17 | extension Sequence {
18 | /// Returns an array with the contents of this sequence, shuffled.
19 | func shuffled() -> [Element] {
20 | var result = Array(self)
21 | result.shuffle()
22 | return result
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Sources/CollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Anchors
3 |
4 | class CollectionViewCell: UICollectionViewCell {
5 | let label = UILabel()
6 |
7 | override func didMoveToSuperview() {
8 | super.didMoveToSuperview()
9 |
10 | addSubview(label)
11 | activate(
12 | label.anchor.center
13 | )
14 |
15 | backgroundColor = UIColor(hex: "#e67e22")
16 | layer.cornerRadius = 5
17 | layer.masksToBounds = true
18 |
19 | label.font = UIFont.boldSystemFont(ofSize: 20)
20 | label.textColor = .white
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Sources/CollectionViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import DeepDiff
3 | import Anchors
4 |
5 | class CollectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
6 |
7 | var collectionView: UICollectionView!
8 | var items: [Int] = []
9 |
10 | override func viewDidLoad() {
11 | super.viewDidLoad()
12 | view.backgroundColor = UIColor.white
13 |
14 | let layout = UICollectionViewFlowLayout()
15 | layout.minimumLineSpacing = 10
16 | layout.minimumInteritemSpacing = 10
17 |
18 | collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
19 | collectionView.dataSource = self
20 | collectionView.delegate = self
21 | collectionView.contentInset = UIEdgeInsets(top: 15, left: 15, bottom: 10, right: 15)
22 | collectionView.backgroundColor = .white
23 |
24 | view.addSubview(collectionView)
25 | activate(
26 | collectionView.anchor.edges
27 | )
28 |
29 | collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
30 | navigationItem.rightBarButtonItem = UIBarButtonItem(
31 | title: "Reload", style: .plain, target: self, action: #selector(reload)
32 | )
33 | }
34 |
35 | @objc func reload() {
36 | let oldItems = self.items
37 | let items = DataSet.generateItems()
38 | let changes = diff(old: oldItems, new: items)
39 |
40 | let exception = tryBlock {
41 | self.collectionView.reload(changes: changes, updateData: {
42 | self.items = items
43 | })
44 | }
45 |
46 | if let exception = exception {
47 | print(exception as Any)
48 | print(oldItems)
49 | print(items)
50 | print(changes)
51 | }
52 | }
53 |
54 | // MARK: - UICollectionViewDataSource
55 |
56 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
57 | return items.count
58 | }
59 |
60 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
61 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
62 | let item = items[indexPath.item]
63 |
64 | cell.label.text = "\(item)"
65 |
66 | return cell
67 | }
68 |
69 | // MARK: - UICollectionViewDelegateFlowLayout
70 |
71 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
72 |
73 | let size = collectionView.frame.size.width / 5
74 | return CGSize(width: size, height: size)
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Sources/Color+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Color+Extensions.swift
3 | // DeepDiffDemo
4 | //
5 | // Created by khoa on 04/01/2019.
6 | // Copyright © 2019 onmyway133. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension UIColor {
13 |
14 | /// Init color from hex string
15 | ///
16 | /// - Parameter hex: A hex string, with or without #
17 | public convenience init(hex: String) {
18 | let hex = hex.replacingOccurrences(of: "#", with: "")
19 |
20 | // Need 6 characters
21 | guard hex.count == 6 else {
22 | self.init(white: 1.0, alpha: 1.0)
23 | return
24 | }
25 |
26 | self.init(
27 | red: CGFloat((Int(hex, radix: 16)! >> 16) & 0xFF) / 255.0,
28 | green: CGFloat((Int(hex, radix: 16)! >> 8) & 0xFF) / 255.0,
29 | blue: CGFloat((Int(hex, radix: 16)!) & 0xFF) / 255.0, alpha: 1.0)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Sources/DataSet.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | struct DataSet {
4 | static func generateItems() -> [Int] {
5 | let count = Int(arc4random_uniform(20)) + 10
6 | let items = Array(0..
5 |
6 | NS_INLINE NSException * _Nullable tryBlock(void(^_Nonnull tryBlock)(void)) {
7 | @try {
8 | tryBlock();
9 | }
10 | @catch (NSException *exception) {
11 | return exception;
12 | }
13 | return nil;
14 | }
15 |
16 | #endif /* ExceptionCatcher_h */
17 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Sources/TableViewCell.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Anchors
3 |
4 | class TableViewCell: UITableViewCell {
5 | let label = UILabel()
6 | let container = UIView()
7 |
8 | override func didMoveToSuperview() {
9 | super.didMoveToSuperview()
10 |
11 | contentView.addSubview(container)
12 | container.addSubview(label)
13 | activate(
14 | container.anchor.edges.insets(UIEdgeInsets(top: 5, left: 10, bottom: -5, right: -10)),
15 | label.anchor.center
16 | )
17 |
18 | container.backgroundColor = UIColor(hex: "#9b59b6")
19 | container.layer.cornerRadius = 5
20 | container.layer.masksToBounds = true
21 |
22 | label.font = UIFont.boldSystemFont(ofSize: 20)
23 | label.textColor = .white
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/DeepDiffDemo/Sources/TableViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import DeepDiff
3 | import Anchors
4 |
5 | class TableViewController: UIViewController, UITableViewDataSource {
6 |
7 | var tableView: UITableView!
8 | var items: [Int] = []
9 |
10 | override func viewDidLoad() {
11 | super.viewDidLoad()
12 | view.backgroundColor = UIColor.white
13 |
14 | tableView = UITableView()
15 | tableView.dataSource = self
16 | tableView.backgroundColor = .white
17 | tableView.rowHeight = 56
18 | tableView.contentInset = UIEdgeInsets(top: 6, left: 0, bottom: 0, right: 0)
19 | tableView.separatorStyle = .none
20 |
21 | view.addSubview(tableView)
22 | activate(
23 | tableView.anchor.edges
24 | )
25 |
26 | tableView.register(TableViewCell.self, forCellReuseIdentifier: "Cell")
27 | navigationItem.rightBarButtonItem = UIBarButtonItem(
28 | title: "Reload", style: .plain, target: self, action: #selector(reload)
29 | )
30 | }
31 |
32 | @objc func reload() {
33 | let oldItems = self.items
34 | let items = DataSet.generateItems()
35 | let changes = diff(old: oldItems, new: items)
36 |
37 | let exception = tryBlock {
38 | self.tableView.reload(changes: changes, updateData: {
39 | self.items = items
40 | })
41 | }
42 |
43 | if let exception = exception {
44 | print(exception as Any)
45 | print(oldItems)
46 | print(items)
47 | print(changes)
48 | }
49 | }
50 |
51 | // MARK: - UITableViewDataSource
52 |
53 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
54 | return items.count
55 | }
56 |
57 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
58 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell
59 | let item = items[indexPath.item]
60 |
61 | cell.label.text = "\(item)"
62 |
63 | return cell
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/Podfile:
--------------------------------------------------------------------------------
1 | use_frameworks!
2 |
3 | platform :ios, '9.0'
4 |
5 | target 'DeepDiffDemo' do
6 | pod 'DeepDiff', path: '../../'
7 | pod 'Anchors'
8 | end
9 |
10 |
--------------------------------------------------------------------------------
/Example/DeepDiffDemo/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Anchors (2.5.0)
3 | - DeepDiff (2.2.0)
4 |
5 | DEPENDENCIES:
6 | - Anchors
7 | - DeepDiff (from `../../`)
8 |
9 | SPEC REPOS:
10 | https://github.com/cocoapods/specs.git:
11 | - Anchors
12 |
13 | EXTERNAL SOURCES:
14 | DeepDiff:
15 | :path: "../../"
16 |
17 | SPEC CHECKSUMS:
18 | Anchors: c039992ebab2fa53deccac429031a46debfbdc5a
19 | DeepDiff: e329bc46dd14ca788d8ec08d34420799ba1d77f2
20 |
21 | PODFILE CHECKSUM: 944cff9288676684ea5bc7da8f78378a41eda706
22 |
23 | COCOAPODS: 1.7.0.beta.3
24 |
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/DeepDiffTexture.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 672FD59DC910191741A0E920 /* Pods_DeepDiffTexture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2C701A5B7F5F39295393994B /* Pods_DeepDiffTexture.framework */; };
11 | D2A5121B222532B300474ABB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5121A222532B300474ABB /* AppDelegate.swift */; };
12 | D2A5121D222532B300474ABB /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5121C222532B300474ABB /* ViewController.swift */; };
13 | D2A51222222532B400474ABB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D2A51221222532B400474ABB /* Assets.xcassets */; };
14 | D2A51225222532B400474ABB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D2A51223222532B400474ABB /* LaunchScreen.storyboard */; };
15 | D2A51230222533C100474ABB /* RootNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5122C222533C000474ABB /* RootNode.swift */; };
16 | D2A51231222533C100474ABB /* TableCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5122D222533C100474ABB /* TableCellNode.swift */; };
17 | D2A51232222533C100474ABB /* ASTableNodeExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5122E222533C100474ABB /* ASTableNodeExtension.swift */; };
18 | D2A51233222533C100474ABB /* TextureTableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5122F222533C100474ABB /* TextureTableController.swift */; };
19 | D2A51235222533EE00474ABB /* DataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A51234222533EE00474ABB /* DataSet.swift */; };
20 | /* End PBXBuildFile section */
21 |
22 | /* Begin PBXFileReference section */
23 | 2C701A5B7F5F39295393994B /* Pods_DeepDiffTexture.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DeepDiffTexture.framework; sourceTree = BUILT_PRODUCTS_DIR; };
24 | 3288319DD0BF7993DFD540DD /* Pods-DeepDiffTexture.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DeepDiffTexture.release.xcconfig"; path = "Target Support Files/Pods-DeepDiffTexture/Pods-DeepDiffTexture.release.xcconfig"; sourceTree = ""; };
25 | 6B9DA9EB85B15CF2F6ECA5D2 /* Pods-DeepDiffTexture.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DeepDiffTexture.debug.xcconfig"; path = "Target Support Files/Pods-DeepDiffTexture/Pods-DeepDiffTexture.debug.xcconfig"; sourceTree = ""; };
26 | D2A51217222532B300474ABB /* DeepDiffTexture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DeepDiffTexture.app; sourceTree = BUILT_PRODUCTS_DIR; };
27 | D2A5121A222532B300474ABB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
28 | D2A5121C222532B300474ABB /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
29 | D2A51221222532B400474ABB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
30 | D2A51224222532B400474ABB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
31 | D2A51226222532B400474ABB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
32 | D2A5122C222533C000474ABB /* RootNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootNode.swift; sourceTree = ""; };
33 | D2A5122D222533C100474ABB /* TableCellNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableCellNode.swift; sourceTree = ""; };
34 | D2A5122E222533C100474ABB /* ASTableNodeExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASTableNodeExtension.swift; sourceTree = ""; };
35 | D2A5122F222533C100474ABB /* TextureTableController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextureTableController.swift; sourceTree = ""; };
36 | D2A51234222533EE00474ABB /* DataSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSet.swift; sourceTree = ""; };
37 | /* End PBXFileReference section */
38 |
39 | /* Begin PBXFrameworksBuildPhase section */
40 | D2A51214222532B300474ABB /* Frameworks */ = {
41 | isa = PBXFrameworksBuildPhase;
42 | buildActionMask = 2147483647;
43 | files = (
44 | 672FD59DC910191741A0E920 /* Pods_DeepDiffTexture.framework in Frameworks */,
45 | );
46 | runOnlyForDeploymentPostprocessing = 0;
47 | };
48 | /* End PBXFrameworksBuildPhase section */
49 |
50 | /* Begin PBXGroup section */
51 | 34F7BC903458E0C3F2B79748 /* Frameworks */ = {
52 | isa = PBXGroup;
53 | children = (
54 | 2C701A5B7F5F39295393994B /* Pods_DeepDiffTexture.framework */,
55 | );
56 | name = Frameworks;
57 | sourceTree = "";
58 | };
59 | 7CEE2017C94121AA247996C3 /* Pods */ = {
60 | isa = PBXGroup;
61 | children = (
62 | 6B9DA9EB85B15CF2F6ECA5D2 /* Pods-DeepDiffTexture.debug.xcconfig */,
63 | 3288319DD0BF7993DFD540DD /* Pods-DeepDiffTexture.release.xcconfig */,
64 | );
65 | path = Pods;
66 | sourceTree = "";
67 | };
68 | D2A5120E222532B300474ABB = {
69 | isa = PBXGroup;
70 | children = (
71 | D2A51219222532B300474ABB /* DeepDiffTexture */,
72 | D2A51218222532B300474ABB /* Products */,
73 | 7CEE2017C94121AA247996C3 /* Pods */,
74 | 34F7BC903458E0C3F2B79748 /* Frameworks */,
75 | );
76 | sourceTree = "";
77 | };
78 | D2A51218222532B300474ABB /* Products */ = {
79 | isa = PBXGroup;
80 | children = (
81 | D2A51217222532B300474ABB /* DeepDiffTexture.app */,
82 | );
83 | name = Products;
84 | sourceTree = "";
85 | };
86 | D2A51219222532B300474ABB /* DeepDiffTexture */ = {
87 | isa = PBXGroup;
88 | children = (
89 | D2A5122E222533C100474ABB /* ASTableNodeExtension.swift */,
90 | D2A5122C222533C000474ABB /* RootNode.swift */,
91 | D2A5122D222533C100474ABB /* TableCellNode.swift */,
92 | D2A5122F222533C100474ABB /* TextureTableController.swift */,
93 | D2A5121A222532B300474ABB /* AppDelegate.swift */,
94 | D2A5121C222532B300474ABB /* ViewController.swift */,
95 | D2A51221222532B400474ABB /* Assets.xcassets */,
96 | D2A51223222532B400474ABB /* LaunchScreen.storyboard */,
97 | D2A51226222532B400474ABB /* Info.plist */,
98 | D2A51234222533EE00474ABB /* DataSet.swift */,
99 | );
100 | path = DeepDiffTexture;
101 | sourceTree = "";
102 | };
103 | /* End PBXGroup section */
104 |
105 | /* Begin PBXNativeTarget section */
106 | D2A51216222532B300474ABB /* DeepDiffTexture */ = {
107 | isa = PBXNativeTarget;
108 | buildConfigurationList = D2A51229222532B400474ABB /* Build configuration list for PBXNativeTarget "DeepDiffTexture" */;
109 | buildPhases = (
110 | 662E697AACAED36B0689A090 /* [CP] Check Pods Manifest.lock */,
111 | D2A51213222532B300474ABB /* Sources */,
112 | D2A51214222532B300474ABB /* Frameworks */,
113 | D2A51215222532B300474ABB /* Resources */,
114 | 5BF10955819EB6234D82F7D2 /* [CP] Embed Pods Frameworks */,
115 | );
116 | buildRules = (
117 | );
118 | dependencies = (
119 | );
120 | name = DeepDiffTexture;
121 | productName = DeepDiffTexture;
122 | productReference = D2A51217222532B300474ABB /* DeepDiffTexture.app */;
123 | productType = "com.apple.product-type.application";
124 | };
125 | /* End PBXNativeTarget section */
126 |
127 | /* Begin PBXProject section */
128 | D2A5120F222532B300474ABB /* Project object */ = {
129 | isa = PBXProject;
130 | attributes = {
131 | LastSwiftUpdateCheck = 1010;
132 | LastUpgradeCheck = 1010;
133 | ORGANIZATIONNAME = "Khoa Pham";
134 | TargetAttributes = {
135 | D2A51216222532B300474ABB = {
136 | CreatedOnToolsVersion = 10.1;
137 | };
138 | };
139 | };
140 | buildConfigurationList = D2A51212222532B300474ABB /* Build configuration list for PBXProject "DeepDiffTexture" */;
141 | compatibilityVersion = "Xcode 9.3";
142 | developmentRegion = en;
143 | hasScannedForEncodings = 0;
144 | knownRegions = (
145 | en,
146 | Base,
147 | );
148 | mainGroup = D2A5120E222532B300474ABB;
149 | productRefGroup = D2A51218222532B300474ABB /* Products */;
150 | projectDirPath = "";
151 | projectRoot = "";
152 | targets = (
153 | D2A51216222532B300474ABB /* DeepDiffTexture */,
154 | );
155 | };
156 | /* End PBXProject section */
157 |
158 | /* Begin PBXResourcesBuildPhase section */
159 | D2A51215222532B300474ABB /* Resources */ = {
160 | isa = PBXResourcesBuildPhase;
161 | buildActionMask = 2147483647;
162 | files = (
163 | D2A51225222532B400474ABB /* LaunchScreen.storyboard in Resources */,
164 | D2A51222222532B400474ABB /* Assets.xcassets in Resources */,
165 | );
166 | runOnlyForDeploymentPostprocessing = 0;
167 | };
168 | /* End PBXResourcesBuildPhase section */
169 |
170 | /* Begin PBXShellScriptBuildPhase section */
171 | 5BF10955819EB6234D82F7D2 /* [CP] Embed Pods Frameworks */ = {
172 | isa = PBXShellScriptBuildPhase;
173 | buildActionMask = 2147483647;
174 | files = (
175 | );
176 | inputFileListPaths = (
177 | "${PODS_ROOT}/Target Support Files/Pods-DeepDiffTexture/Pods-DeepDiffTexture-frameworks-${CONFIGURATION}-input-files.xcfilelist",
178 | );
179 | name = "[CP] Embed Pods Frameworks";
180 | outputFileListPaths = (
181 | "${PODS_ROOT}/Target Support Files/Pods-DeepDiffTexture/Pods-DeepDiffTexture-frameworks-${CONFIGURATION}-output-files.xcfilelist",
182 | );
183 | runOnlyForDeploymentPostprocessing = 0;
184 | shellPath = /bin/sh;
185 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-DeepDiffTexture/Pods-DeepDiffTexture-frameworks.sh\"\n";
186 | showEnvVarsInLog = 0;
187 | };
188 | 662E697AACAED36B0689A090 /* [CP] Check Pods Manifest.lock */ = {
189 | isa = PBXShellScriptBuildPhase;
190 | buildActionMask = 2147483647;
191 | files = (
192 | );
193 | inputFileListPaths = (
194 | );
195 | inputPaths = (
196 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
197 | "${PODS_ROOT}/Manifest.lock",
198 | );
199 | name = "[CP] Check Pods Manifest.lock";
200 | outputFileListPaths = (
201 | );
202 | outputPaths = (
203 | "$(DERIVED_FILE_DIR)/Pods-DeepDiffTexture-checkManifestLockResult.txt",
204 | );
205 | runOnlyForDeploymentPostprocessing = 0;
206 | shellPath = /bin/sh;
207 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
208 | showEnvVarsInLog = 0;
209 | };
210 | /* End PBXShellScriptBuildPhase section */
211 |
212 | /* Begin PBXSourcesBuildPhase section */
213 | D2A51213222532B300474ABB /* Sources */ = {
214 | isa = PBXSourcesBuildPhase;
215 | buildActionMask = 2147483647;
216 | files = (
217 | D2A51231222533C100474ABB /* TableCellNode.swift in Sources */,
218 | D2A5121D222532B300474ABB /* ViewController.swift in Sources */,
219 | D2A51233222533C100474ABB /* TextureTableController.swift in Sources */,
220 | D2A51232222533C100474ABB /* ASTableNodeExtension.swift in Sources */,
221 | D2A51230222533C100474ABB /* RootNode.swift in Sources */,
222 | D2A51235222533EE00474ABB /* DataSet.swift in Sources */,
223 | D2A5121B222532B300474ABB /* AppDelegate.swift in Sources */,
224 | );
225 | runOnlyForDeploymentPostprocessing = 0;
226 | };
227 | /* End PBXSourcesBuildPhase section */
228 |
229 | /* Begin PBXVariantGroup section */
230 | D2A51223222532B400474ABB /* LaunchScreen.storyboard */ = {
231 | isa = PBXVariantGroup;
232 | children = (
233 | D2A51224222532B400474ABB /* Base */,
234 | );
235 | name = LaunchScreen.storyboard;
236 | sourceTree = "";
237 | };
238 | /* End PBXVariantGroup section */
239 |
240 | /* Begin XCBuildConfiguration section */
241 | D2A51227222532B400474ABB /* Debug */ = {
242 | isa = XCBuildConfiguration;
243 | buildSettings = {
244 | ALWAYS_SEARCH_USER_PATHS = NO;
245 | CLANG_ANALYZER_NONNULL = YES;
246 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
247 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
248 | CLANG_CXX_LIBRARY = "libc++";
249 | CLANG_ENABLE_MODULES = YES;
250 | CLANG_ENABLE_OBJC_ARC = YES;
251 | CLANG_ENABLE_OBJC_WEAK = YES;
252 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
253 | CLANG_WARN_BOOL_CONVERSION = YES;
254 | CLANG_WARN_COMMA = YES;
255 | CLANG_WARN_CONSTANT_CONVERSION = YES;
256 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
257 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
258 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
259 | CLANG_WARN_EMPTY_BODY = YES;
260 | CLANG_WARN_ENUM_CONVERSION = YES;
261 | CLANG_WARN_INFINITE_RECURSION = YES;
262 | CLANG_WARN_INT_CONVERSION = YES;
263 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
264 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
265 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
266 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
267 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
268 | CLANG_WARN_STRICT_PROTOTYPES = YES;
269 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
270 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
271 | CLANG_WARN_UNREACHABLE_CODE = YES;
272 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
273 | CODE_SIGN_IDENTITY = "iPhone Developer";
274 | COPY_PHASE_STRIP = NO;
275 | DEBUG_INFORMATION_FORMAT = dwarf;
276 | ENABLE_STRICT_OBJC_MSGSEND = YES;
277 | ENABLE_TESTABILITY = YES;
278 | GCC_C_LANGUAGE_STANDARD = gnu11;
279 | GCC_DYNAMIC_NO_PIC = NO;
280 | GCC_NO_COMMON_BLOCKS = YES;
281 | GCC_OPTIMIZATION_LEVEL = 0;
282 | GCC_PREPROCESSOR_DEFINITIONS = (
283 | "DEBUG=1",
284 | "$(inherited)",
285 | );
286 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
287 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
288 | GCC_WARN_UNDECLARED_SELECTOR = YES;
289 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
290 | GCC_WARN_UNUSED_FUNCTION = YES;
291 | GCC_WARN_UNUSED_VARIABLE = YES;
292 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
293 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
294 | MTL_FAST_MATH = YES;
295 | ONLY_ACTIVE_ARCH = YES;
296 | SDKROOT = iphoneos;
297 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
298 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
299 | };
300 | name = Debug;
301 | };
302 | D2A51228222532B400474ABB /* Release */ = {
303 | isa = XCBuildConfiguration;
304 | buildSettings = {
305 | ALWAYS_SEARCH_USER_PATHS = NO;
306 | CLANG_ANALYZER_NONNULL = YES;
307 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
308 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
309 | CLANG_CXX_LIBRARY = "libc++";
310 | CLANG_ENABLE_MODULES = YES;
311 | CLANG_ENABLE_OBJC_ARC = YES;
312 | CLANG_ENABLE_OBJC_WEAK = YES;
313 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
314 | CLANG_WARN_BOOL_CONVERSION = YES;
315 | CLANG_WARN_COMMA = YES;
316 | CLANG_WARN_CONSTANT_CONVERSION = YES;
317 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
318 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
319 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
320 | CLANG_WARN_EMPTY_BODY = YES;
321 | CLANG_WARN_ENUM_CONVERSION = YES;
322 | CLANG_WARN_INFINITE_RECURSION = YES;
323 | CLANG_WARN_INT_CONVERSION = YES;
324 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
325 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
326 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
327 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
328 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
329 | CLANG_WARN_STRICT_PROTOTYPES = YES;
330 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
331 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
332 | CLANG_WARN_UNREACHABLE_CODE = YES;
333 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
334 | CODE_SIGN_IDENTITY = "iPhone Developer";
335 | COPY_PHASE_STRIP = NO;
336 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
337 | ENABLE_NS_ASSERTIONS = NO;
338 | ENABLE_STRICT_OBJC_MSGSEND = YES;
339 | GCC_C_LANGUAGE_STANDARD = gnu11;
340 | GCC_NO_COMMON_BLOCKS = YES;
341 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
342 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
343 | GCC_WARN_UNDECLARED_SELECTOR = YES;
344 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
345 | GCC_WARN_UNUSED_FUNCTION = YES;
346 | GCC_WARN_UNUSED_VARIABLE = YES;
347 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
348 | MTL_ENABLE_DEBUG_INFO = NO;
349 | MTL_FAST_MATH = YES;
350 | SDKROOT = iphoneos;
351 | SWIFT_COMPILATION_MODE = wholemodule;
352 | SWIFT_OPTIMIZATION_LEVEL = "-O";
353 | VALIDATE_PRODUCT = YES;
354 | };
355 | name = Release;
356 | };
357 | D2A5122A222532B400474ABB /* Debug */ = {
358 | isa = XCBuildConfiguration;
359 | baseConfigurationReference = 6B9DA9EB85B15CF2F6ECA5D2 /* Pods-DeepDiffTexture.debug.xcconfig */;
360 | buildSettings = {
361 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
362 | CODE_SIGN_STYLE = Automatic;
363 | DEVELOPMENT_TEAM = T78DK947F2;
364 | INFOPLIST_FILE = DeepDiffTexture/Info.plist;
365 | LD_RUNPATH_SEARCH_PATHS = (
366 | "$(inherited)",
367 | "@executable_path/Frameworks",
368 | );
369 | PRODUCT_BUNDLE_IDENTIFIER = com.onmyway133.DeepDiffTexture;
370 | PRODUCT_NAME = "$(TARGET_NAME)";
371 | SWIFT_VERSION = 4.2;
372 | TARGETED_DEVICE_FAMILY = "1,2";
373 | };
374 | name = Debug;
375 | };
376 | D2A5122B222532B400474ABB /* Release */ = {
377 | isa = XCBuildConfiguration;
378 | baseConfigurationReference = 3288319DD0BF7993DFD540DD /* Pods-DeepDiffTexture.release.xcconfig */;
379 | buildSettings = {
380 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
381 | CODE_SIGN_STYLE = Automatic;
382 | DEVELOPMENT_TEAM = T78DK947F2;
383 | INFOPLIST_FILE = DeepDiffTexture/Info.plist;
384 | LD_RUNPATH_SEARCH_PATHS = (
385 | "$(inherited)",
386 | "@executable_path/Frameworks",
387 | );
388 | PRODUCT_BUNDLE_IDENTIFIER = com.onmyway133.DeepDiffTexture;
389 | PRODUCT_NAME = "$(TARGET_NAME)";
390 | SWIFT_VERSION = 4.2;
391 | TARGETED_DEVICE_FAMILY = "1,2";
392 | };
393 | name = Release;
394 | };
395 | /* End XCBuildConfiguration section */
396 |
397 | /* Begin XCConfigurationList section */
398 | D2A51212222532B300474ABB /* Build configuration list for PBXProject "DeepDiffTexture" */ = {
399 | isa = XCConfigurationList;
400 | buildConfigurations = (
401 | D2A51227222532B400474ABB /* Debug */,
402 | D2A51228222532B400474ABB /* Release */,
403 | );
404 | defaultConfigurationIsVisible = 0;
405 | defaultConfigurationName = Release;
406 | };
407 | D2A51229222532B400474ABB /* Build configuration list for PBXNativeTarget "DeepDiffTexture" */ = {
408 | isa = XCConfigurationList;
409 | buildConfigurations = (
410 | D2A5122A222532B400474ABB /* Debug */,
411 | D2A5122B222532B400474ABB /* Release */,
412 | );
413 | defaultConfigurationIsVisible = 0;
414 | defaultConfigurationName = Release;
415 | };
416 | /* End XCConfigurationList section */
417 | };
418 | rootObject = D2A5120F222532B300474ABB /* Project object */;
419 | }
420 |
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/DeepDiffTexture.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/DeepDiffTexture.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/DeepDiffTexture.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/DeepDiffTexture.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/DeepDiffTexture/ASTableNodeExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ASTableNodeExtension.swift
3 | // DeepDiffDemo
4 | //
5 | // Created by Gungor Basa on 26.02.2018.
6 | // Copyright © 2018 onmyway133. All rights reserved.
7 | //
8 |
9 | import AsyncDisplayKit
10 | import DeepDiff
11 |
12 | extension ASTableNode {
13 |
14 | /// Animate reload in a batch update
15 | ///
16 | /// - Parameters:
17 | /// - changes: The changes from diff
18 | /// - section: The section that all calculated IndexPath belong
19 | /// - insertionAnimation: The animation for insert rows
20 | /// - deletionAnimation: The animation for delete rows
21 | /// - replacementAnimation: The animation for reload rows
22 | /// - completion: Called when operation completes
23 | public func reload(
24 | changes: [Change],
25 | section: Int = 0,
26 | insertionAnimation: UITableView.RowAnimation = .automatic,
27 | deletionAnimation: UITableView.RowAnimation = .automatic,
28 | replacementAnimation: UITableView.RowAnimation = .automatic,
29 | completion: @escaping (Bool) -> Void) {
30 |
31 | let changesWithIndexPath = IndexPathConverter().convert(changes: changes, section: section)
32 |
33 | // reloadRows needs to be called outside the batch
34 | performBatchUpdates({
35 | internalBatchUpdates(changesWithIndexPath: changesWithIndexPath,
36 | insertionAnimation: insertionAnimation,
37 | deletionAnimation: deletionAnimation)
38 | }, completion: completion)
39 |
40 | changesWithIndexPath.replaces.executeIfPresent {
41 | self.reloadRows(at: $0, with: replacementAnimation)
42 | }
43 | }
44 |
45 | // MARK: - Helper
46 |
47 | private func internalBatchUpdates(changesWithIndexPath: ChangeWithIndexPath,
48 | insertionAnimation: UITableView.RowAnimation,
49 | deletionAnimation: UITableView.RowAnimation) {
50 | changesWithIndexPath.deletes.executeIfPresent {
51 | deleteRows(at: $0, with: deletionAnimation)
52 | }
53 |
54 | changesWithIndexPath.inserts.executeIfPresent {
55 | insertRows(at: $0, with: insertionAnimation)
56 | }
57 |
58 | changesWithIndexPath.moves.executeIfPresent {
59 | $0.forEach { move in
60 | moveRow(at: move.from, to: move.to)
61 | }
62 | }
63 | }
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/DeepDiffTexture/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // DeepDiffTexture
4 | //
5 | // Created by khoa on 26/02/2019.
6 | // Copyright © 2019 Khoa Pham. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | window = UIWindow(frame: UIScreen.main.bounds)
19 |
20 | let textureController = TextureTableController()
21 | textureController.tabBarItem.image = UIImage(named: "table")
22 | textureController.title = "ASTableNode"
23 |
24 | let tabController = UITabBarController()
25 |
26 | tabController.viewControllers = [
27 | UINavigationController(rootViewController: textureController)
28 | ]
29 |
30 | UINavigationBar.appearance().barTintColor = UIColor.brown
31 |
32 | window?.rootViewController = tabController
33 | window?.makeKeyAndVisible()
34 |
35 | return true
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/DeepDiffTexture/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/DeepDiffTexture/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/DeepDiffTexture/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/DeepDiffTexture/DataSet.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataSet.swift
3 | // DeepDiffTexture
4 | //
5 | // Created by khoa on 26/02/2019.
6 | // Copyright © 2019 Khoa Pham. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct DataSet {
12 | static func generateItems() -> [Int] {
13 | let count = Int(arc4random_uniform(20)) + 10
14 | let items = Array(0..
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/DeepDiffTexture/RootNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RootNode.swift
3 | // DeepDiffDemo
4 | //
5 | // Created by Gungor Basa on 26.02.2018.
6 | // Copyright © 2018 onmyway133. All rights reserved.
7 | //
8 |
9 | import AsyncDisplayKit
10 |
11 | class RootNode: ASDisplayNode {
12 |
13 | let tableNode: ASTableNode = {
14 | let node = ASTableNode()
15 | let full = ASDimension(unit: .fraction, value: 1.0)
16 | node.style.preferredLayoutSize = ASLayoutSize(width: full, height: full)
17 |
18 | return node
19 | }()
20 |
21 | override init() {
22 | super.init()
23 | automaticallyManagesSubnodes = true
24 | }
25 |
26 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
27 | return ASAbsoluteLayoutSpec(children: [tableNode])
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/DeepDiffTexture/TableCellNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableNode.swift
3 | // DeepDiffDemo
4 | //
5 | // Created by Gungor Basa on 26.02.2018.
6 | // Copyright © 2018 onmyway133. All rights reserved.
7 | //
8 |
9 | import AsyncDisplayKit
10 |
11 | class TableCellNode: ASCellNode {
12 |
13 | var text = ASTextNode()
14 |
15 | override init() {
16 | super.init()
17 | automaticallyManagesSubnodes = true
18 | self.backgroundColor = .orange
19 | self.style.preferredSize.height = CGFloat(50)
20 | }
21 |
22 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
23 | return ASStackLayoutSpec(direction: .vertical, spacing: 0, justifyContent: .center, alignItems: .stretch, children: [text])
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/DeepDiffTexture/TextureTableController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextureTableController.swift
3 | // DeepDiffDemo
4 | //
5 | // Created by Gungor Basa on 26.02.2018.
6 | // Copyright © 2018 onmyway133. All rights reserved.
7 | //
8 |
9 | import AsyncDisplayKit
10 | import DeepDiff
11 |
12 | class TextureTableController: ASViewController {
13 |
14 | var items: [Int] = []
15 | let rootNode = RootNode()
16 |
17 | init() {
18 | super.init(node: rootNode)
19 | node.backgroundColor = .white
20 | title = "ASTableNode"
21 | }
22 |
23 | required init?(coder aDecoder: NSCoder) {
24 | fatalError("init(coder:) has not been implemented")
25 | }
26 |
27 | override func viewDidLoad() {
28 | super.viewDidLoad()
29 | navigationItem.rightBarButtonItem = UIBarButtonItem(
30 | title: "Reload", style: .plain, target: self, action: #selector(reload)
31 | )
32 |
33 | rootNode.tableNode.dataSource = self
34 | reload()
35 | }
36 |
37 | @objc func reload() {
38 | let oldItems = items
39 | items = DataSet.generateItems()
40 | let changes = diff(old: oldItems, new: items)
41 |
42 | self.rootNode.tableNode.reload(changes: changes, completion: { _ in })
43 | }
44 | }
45 |
46 | extension TextureTableController: ASTableDataSource {
47 |
48 | func numberOfSections(in tableNode: ASTableNode) -> Int {
49 | return 1
50 | }
51 |
52 | func tableNode(_ tableNode: ASTableNode, numberOfRowsInSection section: Int) -> Int {
53 | return items.count
54 | }
55 |
56 |
57 | func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock {
58 | return {
59 | let cell = TableCellNode()
60 |
61 | cell.text.attributedText = NSAttributedString(string: "\(self.items[indexPath.item])")
62 |
63 | return cell
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/DeepDiffTexture/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // DeepDiffTexture
4 | //
5 | // Created by khoa on 26/02/2019.
6 | // Copyright © 2019 Khoa Pham. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 | // Do any additional setup after loading the view, typically from a nib.
16 | }
17 |
18 |
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/Podfile:
--------------------------------------------------------------------------------
1 | use_frameworks!
2 |
3 | platform :ios, '9.0'
4 |
5 | target 'DeepDiffTexture' do
6 | pod 'DeepDiff', path: '../../'
7 | pod 'Anchors'
8 | pod 'Texture'
9 | end
10 |
11 |
--------------------------------------------------------------------------------
/Example/DeepDiffTexture/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Anchors (2.5.0)
3 | - DeepDiff (2.2.0)
4 | - PINCache (3.0.1-beta.6):
5 | - PINCache/Arc-exception-safe (= 3.0.1-beta.6)
6 | - PINCache/Core (= 3.0.1-beta.6)
7 | - PINCache/Arc-exception-safe (3.0.1-beta.6):
8 | - PINCache/Core
9 | - PINCache/Core (3.0.1-beta.6):
10 | - PINOperation (~> 1.1.0)
11 | - PINOperation (1.1.1)
12 | - PINRemoteImage/Core (3.0.0-beta.13):
13 | - PINOperation
14 | - PINRemoteImage/iOS (3.0.0-beta.13):
15 | - PINRemoteImage/Core
16 | - PINRemoteImage/PINCache (3.0.0-beta.13):
17 | - PINCache (= 3.0.1-beta.6)
18 | - PINRemoteImage/Core
19 | - Texture (2.7):
20 | - Texture/PINRemoteImage (= 2.7)
21 | - Texture/Core (2.7)
22 | - Texture/PINRemoteImage (2.7):
23 | - PINRemoteImage/iOS (= 3.0.0-beta.13)
24 | - PINRemoteImage/PINCache
25 | - Texture/Core
26 |
27 | DEPENDENCIES:
28 | - Anchors
29 | - DeepDiff (from `../../`)
30 | - Texture
31 |
32 | SPEC REPOS:
33 | https://github.com/cocoapods/specs.git:
34 | - Anchors
35 | - PINCache
36 | - PINOperation
37 | - PINRemoteImage
38 | - Texture
39 |
40 | EXTERNAL SOURCES:
41 | DeepDiff:
42 | :path: "../../"
43 |
44 | SPEC CHECKSUMS:
45 | Anchors: c039992ebab2fa53deccac429031a46debfbdc5a
46 | DeepDiff: e329bc46dd14ca788d8ec08d34420799ba1d77f2
47 | PINCache: d195fdba255283f7e9900a55e3cced377f431f9b
48 | PINOperation: a6219e6fc9db9c269eb7a7b871ac193bcf400aac
49 | PINRemoteImage: d6d51c5d2adda55f1ce30c96e850b6c4ebd2856a
50 | Texture: 9d7e38965cf22ccd7cd9c249dd78b3f14e70ab6c
51 |
52 | PODFILE CHECKSUM: 317e16bd2e833f10896dad1c9e447a82a97b0e37
53 |
54 | COCOAPODS: 1.7.0.beta.3
55 |
--------------------------------------------------------------------------------
/Info/Info-iOS.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 0.1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Info/Info-macOS.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 0.1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSHumanReadableCopyright
24 | Copyright © 2016 Hyper Interaktiv AS. All rights reserved.
25 | NSPrincipalClass
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Info/Info-tvOS.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 0.1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Info/Info-watchOS.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 0.1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Licensed under the **MIT** license
2 |
3 | > Copyright (c) 2015 Khoa Pham
4 | >
5 | > Permission is hereby granted, free of charge, to any person obtaining
6 | > a copy of this software and associated documentation files (the
7 | > "Software"), to deal in the Software without restriction, including
8 | > without limitation the rights to use, copy, modify, merge, publish,
9 | > distribute, sublicense, and/or sell copies of the Software, and to
10 | > permit persons to whom the Software is furnished to do so, subject to
11 | > the following conditions:
12 | >
13 | > The above copyright notice and this permission notice shall be
14 | > included in all copies or substantial portions of the Software.
15 | >
16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "DeepDiff",
7 | platforms: [
8 | .macOS(.v10_11),
9 | .iOS(.v9),
10 | .tvOS(.v11),
11 | .watchOS(.v6)
12 | ],
13 | products: [
14 | .library(name: "DeepDiff", targets: ["DeepDiff"]),
15 | ],
16 | targets: [
17 | .target(name: "DeepDiff", path: "Sources")
18 | ]
19 | )
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DeepDiff
2 |
3 | ❤️ Support my apps ❤️
4 |
5 | - [Push Hero - pure Swift native macOS application to test push notifications](https://onmyway133.com/pushhero)
6 | - [PastePal - Pasteboard, note and shortcut manager](https://onmyway133.com/pastepal)
7 | - [Quick Check - smart todo manager](https://onmyway133.com/quickcheck)
8 | - [Alias - App and file shortcut manager](https://onmyway133.com/alias)
9 | - [My other apps](https://onmyway133.com/apps/)
10 |
11 | ❤️❤️😇😍🤘❤️❤️
12 |
13 |
14 | [](https://circleci.com/gh/onmyway133/DeepDiff)
15 | [](http://cocoadocs.org/docsets/DeepDiff)
16 | [](https://github.com/Carthage/Carthage)
17 | [](http://cocoadocs.org/docsets/DeepDiff)
18 | [](http://cocoadocs.org/docsets/DeepDiff)
19 | 
20 |
21 | 
22 |
23 | **DeepDiff** tells the difference between 2 collections and the changes as edit steps. It also supports [Texture](https://github.com/TextureGroup/Texture), see [Texture example](https://github.com/onmyway133/DeepDiff/tree/master/Example/DeepDiffTexture)
24 |
25 | - Read more [A better way to update UICollectionView data in Swift with diff framework](https://medium.com/flawless-app-stories/a-better-way-to-update-uicollectionview-data-in-swift-with-diff-framework-924db158db86)
26 | - Checkout [Micro](https://github.com/onmyway133/Micro) Fast diffing and type safe SwiftUI style data source for UICollectionView
27 |
28 |
29 |

30 |

31 |
32 |
33 | ## Usage
34 |
35 | ### Basic
36 |
37 | The result of `diff` is an array of changes, which is `[Change]`. A `Change` can be
38 |
39 | - `.insert`: The item was inserted at an index
40 | - `.delete`: The item was deleted from an index
41 | - `.replace`: The item at this index was replaced by another item
42 | - `.move`: The same item has moved from this index to another index
43 |
44 | By default, there is no `.move`. But since `.move` is just `.delete` followed by `.insert` of the same item, it can be reduced by specifying `reduceMove` to `true`.
45 |
46 | Here are some examples
47 |
48 | ```swift
49 | let old = Array("abc")
50 | let new = Array("bcd")
51 | let changes = diff(old: old, new: new)
52 |
53 | // Delete "a" at index 0
54 | // Insert "d" at index 2
55 | ```
56 |
57 | ```swift
58 | let old = Array("abcd")
59 | let new = Array("adbc")
60 | let changes = diff(old: old, new: new)
61 |
62 | // Move "d" from index 3 to index 1
63 | ```
64 |
65 | ```swift
66 | let old = [
67 | User(id: 1, name: "Captain America"),
68 | User(id: 2, name: "Captain Marvel"),
69 | User(id: 3, name: "Thor"),
70 | ]
71 |
72 | let new = [
73 | User(id: 1, name: "Captain America"),
74 | User(id: 2, name: "The Binary"),
75 | User(id: 3, name: "Thor"),
76 | ]
77 |
78 | let changes = diff(old: old, new: new)
79 |
80 | // Replace user "Captain Marvel" with user "The Binary" at index 1
81 | ```
82 |
83 | ### DiffAware protocol
84 |
85 | Model must conform to `DiffAware` protocol for DeepDiff to work. An model needs to be uniquely identified via `diffId` to tell if there have been any insertions or deletions. In case of same `diffId`, `compareContent` is used to check if any properties have changed, this is for replacement changes.
86 |
87 | ```swift
88 | public protocol DiffAware {
89 | associatedtype DiffId: Hashable
90 |
91 | var diffId: DiffId { get }
92 | static func compareContent(_ a: Self, _ b: Self) -> Bool
93 | }
94 | ```
95 |
96 | Some primitive types like `String`, `Int`, `Character` already conform to `DiffAware`
97 |
98 | ### Animate UITableView and UICollectionView
99 |
100 | Changes to `DataSource` can be animated by using batch update, as guided in [Batch Insertion, Deletion, and Reloading of Rows and Sections](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/TableView_iPhone/ManageInsertDeleteRow/ManageInsertDeleteRow.html#//apple_ref/doc/uid/TP40007451-CH10-SW9)
101 |
102 | Since `Change` returned by `DeepDiff` follows the way batch update works, animating `DataSource` changes is easy.
103 |
104 | For safety, update your data source model inside `updateData` to ensure synchrony inside `performBatchUpdates`
105 |
106 | ```swift
107 | let oldItems = items
108 | let newItems = DataSet.generateNewItems()
109 | let changes = diff(old: oldItems, new: newItems)
110 |
111 | collectionView.reload(changes: changes, section: 2, updateData: {
112 | self.items = newItems
113 | })
114 | ```
115 |
116 | Take a look at [Demo](https://github.com/onmyway133/DeepDiff/tree/master/Example/DeepDiffDemo) where changes are made via random number of items, and the items are shuffled.
117 |
118 | ## How does it work
119 |
120 | ### [Wagner–Fischer](https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm)
121 |
122 | If you recall from school, there is [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance) which counts the minimum edit distance to go from one string to another.
123 |
124 | Based on that, the first version of `DeepDiff` implements Wagner–Fischer, which uses [dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming) to compute the edit steps between 2 strings of characters. `DeepDiff` generalizes this to make it work for any collection.
125 |
126 | Some optimisations made
127 |
128 | - Check empty old or new collection to return early
129 | - Use `diffId` to quickly check that 2 items are not equal
130 | - Follow "We can adapt the algorithm to use less space, O(m) instead of O(mn), since it only requires that the previous row and current row be stored at any one time." to use 2 rows, instead of matrix to reduce memory storage.
131 |
132 | The performance greatly depends on the number of items, the changes and the complexity of the `equal` function.
133 |
134 | `Wagner–Fischer algorithm` has O(mn) complexity, so it should be used for collection with < 100 items.
135 |
136 | ### Heckel
137 |
138 | The current version of `DeepDiff` uses Heckel algorithm as described in [A technique for isolating differences between files](https://dl.acm.org/citation.cfm?id=359467). It works on 2 observations about line occurrences and counters. The result is a bit lengthy compared to the first version, but it runs in linear time.
139 |
140 | Thanks to
141 |
142 | - [Isolating Differences Between Files](https://gist.github.com/ndarville/3166060) for explaining step by step
143 | - [HeckelDiff](https://github.com/mcudich/HeckelDiff) for a clever move reducer based on tracking `deleteOffset`
144 |
145 | ### More
146 |
147 | There are other algorithms that are interesting
148 |
149 | - [An O(ND) Difference Algorithm and Its Variations](http://www.xmailserver.org/diff2.pdf)
150 | - [An O(NP) Sequence Comparison Algorithm](https://publications.mpi-cbg.de/Wu_1990_6334.pdf)
151 |
152 | ## Benchmarks
153 |
154 | Benchmarking is done on real device iPhone 6, with random items made of UUID strings (36 characters including hyphens), just to make comparisons more difficult.
155 |
156 | You can take a look at the code [Benchmark](https://github.com/onmyway133/DeepDiff/tree/master/Example/Benchmark). Test is inspired from [DiffUtil](https://developer.android.com/reference/android/support/v7/util/DiffUtil.html)
157 |
158 | ### Among different frameworks
159 |
160 | Here are several popular diffing frameworks to compare
161 |
162 | - [Differ](https://github.com/tonyarnold/Differ) 1.0.3, originally [Diff.swift](https://github.com/wokalski/Diff.swift)
163 | - [Changeset](https://github.com/osteslag/Changeset) 3.0
164 | - [Dwifft](https://github.com/jflinter/Dwifft) 0.8
165 | - [ListDiff](https://github.com/lxcid/ListDiff) 0.1.0, port from [IGListKit](https://github.com/Instagram/IGListKit/)
166 |
167 | 💪 From 2000 items to 2100 items (100 deletions, 200 insertions)
168 |
169 | ```swift
170 | let (old, new) = generate(count: 2000, removeRange: 100..<200, addRange: 1000..<1200)
171 |
172 | benchmark(name: "DeepDiff", closure: {
173 | _ = DeepDiff.diff(old: old, new: new)
174 | })
175 |
176 | benchmark(name: "Dwifft", closure: {
177 | _ = Dwifft.diff(old, new)
178 | })
179 |
180 | benchmark(name: "Changeset", closure: {
181 | _ = Changeset.edits(from: old, to: new)
182 | })
183 |
184 | benchmark(name: "Differ", closure: {
185 | _ = old.diffTraces(to: new)
186 | })
187 |
188 | benchmark(name: "ListDiff", closure: {
189 | _ = ListDiff.List.diffing(oldArray: old, newArray: new)
190 | })
191 | ```
192 |
193 | **Result**
194 |
195 | ```
196 | DeepDiff: 0.0450611114501953s
197 | Differ: 0.199673891067505s
198 | Dwifft: 149.603884935379s
199 | Changeset: 77.5895738601685s
200 | ListDiff: 0.105544805526733s
201 | ```
202 |
203 | 
204 |
205 | ### Increasing complexity
206 |
207 | Here is how `DeepDiff` handles large number of items and changes
208 |
209 | 💪 From 10000 items to 11000 items (1000 deletions, 2000 insertions)
210 |
211 | ```
212 | DeepDiff: 0.233131170272827s
213 | ```
214 |
215 | 💪 From 20000 items to 22000 items (2000 deletions, 4000 insertions)
216 |
217 | ```
218 | DeepDiff: 0.453393936157227s
219 | ```
220 |
221 | 💪 From 50000 items to 55000 items (5000 deletions, 10000 insertions)
222 |
223 | ```
224 | DeepDiff: 1.04128122329712s
225 | ```
226 |
227 | 💪 From 100000 items to 1000000 items
228 |
229 | ```
230 | Are you sure?
231 | ```
232 |
233 | ## Installation
234 |
235 | ### CocoaPods
236 |
237 | Add the following to your Podfile
238 |
239 | ```ruby
240 | pod 'DeepDiff'
241 | ```
242 |
243 | ### Carthage
244 |
245 | Add the following to your Cartfile
246 |
247 | ```ruby
248 | github "onmyway133/DeepDiff"
249 | ```
250 |
251 | ### Swift Package Manager
252 | Add the following to your Package.swift file
253 |
254 | ```swift
255 | .package(url: "https://github.com/onmyway133/DeepDiff.git", .upToNextMajor(from: "2.3.0"))
256 | ```
257 |
258 | **DeepDiff** can also be installed manually. Just download and drop `Sources` folders in your project.
259 |
260 | ## Author
261 |
262 | Khoa Pham, onmyway133@gmail.com
263 |
264 | ## Contributing
265 |
266 | We would love you to contribute to **DeepDiff**, check the [CONTRIBUTING](https://github.com/onmyway133/DeepDiff/blob/master/CONTRIBUTING.md) file for more info.
267 |
268 | ## License
269 |
270 | **DeepDiff** is available under the MIT license. See the [LICENSE](https://github.com/onmyway133/DeepDiff/blob/master/LICENSE.md) file for more info.
271 |
--------------------------------------------------------------------------------
/Screenshots/Banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/DeepDiff/2639b3a11054d666a4c5ec1b7dd2808e437f3289/Screenshots/Banner.png
--------------------------------------------------------------------------------
/Screenshots/benchmark3d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/DeepDiff/2639b3a11054d666a4c5ec1b7dd2808e437f3289/Screenshots/benchmark3d.png
--------------------------------------------------------------------------------
/Screenshots/collection.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/DeepDiff/2639b3a11054d666a4c5ec1b7dd2808e437f3289/Screenshots/collection.gif
--------------------------------------------------------------------------------
/Screenshots/table.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onmyway133/DeepDiff/2639b3a11054d666a4c5ec1b7dd2808e437f3289/Screenshots/table.gif
--------------------------------------------------------------------------------
/Sources/Shared/Algorithms/Heckel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Heckel.swift
3 | // DeepDiff
4 | //
5 | // Created by Khoa Pham.
6 | // Copyright © 2018 Khoa Pham. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // https://gist.github.com/ndarville/3166060
12 |
13 | public final class Heckel {
14 | // OC and NC can assume three values: 1, 2, and many.
15 | enum Counter {
16 | case zero, one, many
17 |
18 | func increment() -> Counter {
19 | switch self {
20 | case .zero:
21 | return .one
22 | case .one:
23 | return .many
24 | case .many:
25 | return self
26 | }
27 | }
28 | }
29 |
30 | // The symbol table stores three entries for each line
31 | class TableEntry: Equatable {
32 | // The value entry for each line in table has two counters.
33 | // They specify the line's number of occurrences in O and N: OC and NC.
34 | var oldCounter: Counter = .zero
35 | var newCounter: Counter = .zero
36 |
37 | // Aside from the two counters, the line's entry
38 | // also includes a reference to the line's line number in O: OLNO.
39 | // OLNO is only interesting, if OC == 1.
40 | // Alternatively, OLNO would have to assume multiple values or none at all.
41 | var indexesInOld: [Int] = []
42 |
43 | static func ==(lhs: TableEntry, rhs: TableEntry) -> Bool {
44 | return lhs.oldCounter == rhs.oldCounter && lhs.newCounter == rhs.newCounter && lhs.indexesInOld == rhs.indexesInOld
45 | }
46 | }
47 |
48 | // The arrays OA and NA have one entry for each line in their respective files, O and N.
49 | // The arrays contain either:
50 | enum ArrayEntry: Equatable {
51 | // a pointer to the line's symbol table entry, table[line]
52 | case tableEntry(TableEntry)
53 |
54 | // the line's number in the other file (N for OA, O for NA)
55 | case indexInOther(Int)
56 |
57 | public static func == (lhs: ArrayEntry, rhs: ArrayEntry) -> Bool {
58 | switch (lhs, rhs) {
59 | case (.tableEntry(let l), .tableEntry(let r)):
60 | return l == r
61 | case (.indexInOther(let l), .indexInOther(let r)):
62 | return l == r
63 | default:
64 | return false
65 | }
66 | }
67 | }
68 |
69 | public func diff(old: [T], new: [T]) -> [Change] {
70 | // The Symbol Table
71 | // Each line works as the key in the table look-up, i.e. as table[line].
72 | var table: [T.DiffId: TableEntry] = [:]
73 |
74 | // The arrays OA and NA have one entry for each line in their respective files, O and N
75 | var oldArray = [ArrayEntry]()
76 | var newArray = [ArrayEntry]()
77 |
78 | perform1stPass(new: new, table: &table, newArray: &newArray)
79 | perform2ndPass(old: old, table: &table, oldArray: &oldArray)
80 | perform345Pass(newArray: &newArray, oldArray: &oldArray)
81 | let changes = perform6thPass(new: new, old: old, newArray: newArray, oldArray: oldArray)
82 | return changes
83 | }
84 |
85 | private func perform1stPass(
86 | new: [T],
87 | table: inout [T.DiffId: TableEntry],
88 | newArray: inout [ArrayEntry]) {
89 |
90 | // 1st pass
91 | // a. Each line i of file N is read in sequence
92 | new.forEach { item in
93 | // b. An entry for each line i is created in the table, if it doesn't already exist
94 | let entry = table[item.diffId] ?? TableEntry()
95 |
96 | // c. NC for the line's table entry is incremented
97 | entry.newCounter = entry.newCounter.increment()
98 |
99 | // d. NA[i] is set to point to the table entry of line i
100 | newArray.append(.tableEntry(entry))
101 |
102 | //
103 | table[item.diffId] = entry
104 | }
105 | }
106 |
107 | private func perform2ndPass(
108 | old: [T],
109 | table: inout [T.DiffId: TableEntry],
110 | oldArray: inout [ArrayEntry]) {
111 |
112 | // 2nd pass
113 | // Similar to first pass, except it acts on files
114 |
115 | old.enumerated().forEach { tuple in
116 | // old
117 | let entry = table[tuple.element.diffId] ?? TableEntry()
118 |
119 | // oldCounter
120 | entry.oldCounter = entry.oldCounter.increment()
121 |
122 | // lineNumberInOld which is set to the line's number
123 | entry.indexesInOld.append(tuple.offset)
124 |
125 | // oldArray
126 | oldArray.append(.tableEntry(entry))
127 |
128 | //
129 | table[tuple.element.diffId] = entry
130 | }
131 | }
132 |
133 | private func perform345Pass(newArray: inout [ArrayEntry], oldArray: inout [ArrayEntry]) {
134 | // 3rd pass
135 | // a. We use Observation 1:
136 | // If a line occurs only once in each file, then it must be the same line,
137 | // although it may have been moved.
138 | // We use this observation to locate unaltered lines that we
139 | // subsequently exclude from further treatment.
140 | // b. Using this, we only process the lines where OC == NC == 1
141 | // c. As the lines between O and N "must be the same line,
142 | // although it may have been moved", we alter the table pointers
143 | // in OA and NA to the number of the line in the other file.
144 | // d. We also locate unique virtual lines
145 | // immediately before the first and
146 | // immediately after the last lines of the files ???
147 | //
148 | // 4th pass
149 | // a. We use Observation 2:
150 | // If a line has been found to be unaltered,
151 | // and the lines immediately adjacent to it in both files are identical,
152 | // then these lines must be the same line.
153 | // This information can be used to find blocks of unchanged lines.
154 | // b. Using this, we process each entry in ascending order.
155 | // c. If
156 | // NA[i] points to OA[j], and
157 | // NA[i+1] and OA[j+1] contain identical table entry pointers
158 | // then
159 | // OA[j+1] is set to line i+1, and
160 | // NA[i+1] is set to line j+1
161 | //
162 | // 5th pass
163 | // Similar to fourth pass, except:
164 | // It processes each entry in descending order
165 | // It uses j-1 and i-1 instead of j+1 and i+1
166 |
167 | newArray.enumerated().forEach { (indexOfNew, item) in
168 | switch item {
169 | case .tableEntry(let entry):
170 | guard !entry.indexesInOld.isEmpty else {
171 | return
172 | }
173 | let indexOfOld = entry.indexesInOld.removeFirst()
174 | let isObservation1 = entry.newCounter == .one && entry.oldCounter == .one
175 | let isObservation2 = entry.newCounter != .zero && entry.oldCounter != .zero && newArray[indexOfNew] == oldArray[indexOfOld]
176 | guard isObservation1 || isObservation2 else {
177 | return
178 | }
179 | newArray[indexOfNew] = .indexInOther(indexOfOld)
180 | oldArray[indexOfOld] = .indexInOther(indexOfNew)
181 | case .indexInOther(_):
182 | break
183 | }
184 | }
185 | }
186 |
187 | private func perform6thPass(
188 | new: [T],
189 | old: [T],
190 | newArray: [ArrayEntry],
191 | oldArray: [ArrayEntry]) -> [Change] {
192 |
193 | // 6th pass
194 | // At this point following our five passes,
195 | // we have the necessary information contained in NA to tell the differences between O and N.
196 | // This pass uses NA and OA to tell when a line has changed between O and N,
197 | // and how far the change extends.
198 |
199 | // a. Determining a New Line
200 | // Recall our initial description of NA in which we said that the array has either:
201 | // one entry for each line of file N containing either
202 | // a pointer to table[line]
203 | // the line's number in file O
204 |
205 | // Using these two cases, we know that if NA[i] refers
206 | // to an entry in table (case 1), then line i must be new
207 | // We know this, because otherwise, NA[i] would have contained
208 | // the line's number in O (case 2), if it existed in O and N
209 |
210 | // b. Determining the Boundaries of the New Line
211 | // We now know that we are dealing with a new line, but we have yet to figure where the change ends.
212 | // Recall Observation 2:
213 |
214 | // If NA[i] points to OA[j], but NA[i+1] does not
215 | // point to OA[j+1], then line i is the boundary for the alteration.
216 |
217 | // You can look at it this way:
218 | // i : The quick brown fox | j : The quick brown fox
219 | // i+1: jumps over the lazy dog | j+1: jumps over the loafing cat
220 |
221 | // Here, NA[i] == OA[j], but NA[i+1] != OA[j+1].
222 | // This means our boundary is between the two lines.
223 |
224 | var changes = [Change]()
225 | var deleteOffsets = Array(repeating: 0, count: old.count)
226 |
227 | // deletions
228 | do {
229 | var runningOffset = 0
230 |
231 | oldArray.enumerated().forEach { oldTuple in
232 | deleteOffsets[oldTuple.offset] = runningOffset
233 |
234 | guard case .tableEntry = oldTuple.element else {
235 | return
236 | }
237 |
238 | changes.append(.delete(Delete(
239 | item: old[oldTuple.offset],
240 | index: oldTuple.offset
241 | )))
242 |
243 | runningOffset += 1
244 | }
245 | }
246 |
247 | // insertions, replaces, moves
248 | do {
249 | var runningOffset = 0
250 |
251 | newArray.enumerated().forEach { newTuple in
252 | switch newTuple.element {
253 | case .tableEntry:
254 | runningOffset += 1
255 | changes.append(.insert(Insert(
256 | item: new[newTuple.offset],
257 | index: newTuple.offset
258 | )))
259 | case .indexInOther(let oldIndex):
260 | if !isEqual(oldItem: old[oldIndex], newItem: new[newTuple.offset]) {
261 | changes.append(.replace(Replace(
262 | oldItem: old[oldIndex],
263 | newItem: new[newTuple.offset],
264 | index: newTuple.offset
265 | )))
266 | }
267 |
268 | let deleteOffset = deleteOffsets[oldIndex]
269 | // The object is not at the expected position, so move it.
270 | if (oldIndex - deleteOffset + runningOffset) != newTuple.offset {
271 | changes.append(.move(Move(
272 | item: new[newTuple.offset],
273 | fromIndex: oldIndex,
274 | toIndex: newTuple.offset
275 | )))
276 | }
277 | }
278 | }
279 | }
280 |
281 | return changes
282 | }
283 |
284 | func isEqual(oldItem: T, newItem: T) -> Bool {
285 | return T.compareContent(oldItem, newItem)
286 | }
287 | }
288 |
--------------------------------------------------------------------------------
/Sources/Shared/Algorithms/WagnerFischer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WagnerFischer.swift
3 | // DeepDiff
4 | //
5 | // Created by Khoa Pham.
6 | // Copyright © 2018 Khoa Pham. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm
12 |
13 | public final class WagnerFischer {
14 | private let reduceMove: Bool
15 |
16 | public init(reduceMove: Bool = false) {
17 | self.reduceMove = reduceMove
18 | }
19 |
20 | public func diff(old: [T], new: [T]) -> [Change] {
21 | let previousRow = Row()
22 | previousRow.seed(with: new)
23 | let currentRow = Row()
24 |
25 | if let changes = preprocess(old: old, new: new) {
26 | return changes
27 | }
28 |
29 | // row in matrix
30 | old.enumerated().forEach { indexInOld, oldItem in
31 | // reset current row
32 | currentRow.reset(
33 | count: previousRow.slots.count,
34 | indexInOld: indexInOld,
35 | oldItem: oldItem
36 | )
37 |
38 | // column in matrix
39 | new.enumerated().forEach { indexInNew, newItem in
40 | if isEqual(oldItem: old[indexInOld], newItem: new[indexInNew]) {
41 | currentRow.update(indexInNew: indexInNew, previousRow: previousRow)
42 | } else {
43 | currentRow.updateWithMin(
44 | previousRow: previousRow,
45 | indexInNew: indexInNew,
46 | newItem: newItem,
47 | indexInOld: indexInOld,
48 | oldItem: oldItem
49 | )
50 | }
51 | }
52 |
53 | // set previousRow
54 | previousRow.slots = currentRow.slots
55 | }
56 |
57 | let changes = currentRow.lastSlot()
58 | if reduceMove {
59 | return MoveReducer().reduce(changes: changes)
60 | } else {
61 | return changes
62 | }
63 | }
64 |
65 | // MARK: - Helper
66 |
67 | private func isEqual(oldItem: T, newItem: T) -> Bool {
68 | return T.compareContent(oldItem, newItem)
69 | }
70 | }
71 |
72 | // We can adapt the algorithm to use less space, O(m) instead of O(mn),
73 | // since it only requires that the previous row and current row be stored at any one time
74 | class Row {
75 | /// Each slot is a collection of Change
76 | var slots: [[Change]] = []
77 |
78 | /// Seed with .insert from new
79 | func seed(with new: Array) {
80 | // First slot should be empty
81 | slots = Array(repeatElement([], count: new.count + 1))
82 |
83 | // Each slot increases in the number of changes
84 | new.enumerated().forEach { index, item in
85 | let slotIndex = convert(indexInNew: index)
86 | slots[slotIndex] = combine(
87 | slot: slots[slotIndex-1],
88 | change: .insert(Insert(item: item, index: index))
89 | )
90 | }
91 | }
92 |
93 | /// Reset with empty slots
94 | /// First slot is .delete
95 | func reset(count: Int, indexInOld: Int, oldItem: T) {
96 | if slots.isEmpty {
97 | slots = Array(repeatElement([], count: count))
98 | }
99 |
100 | slots[0] = combine(
101 | slot: slots[0],
102 | change: .delete(Delete(item: oldItem, index: indexInOld))
103 | )
104 | }
105 |
106 | /// Use .replace from previousRow
107 | func update(indexInNew: Int, previousRow: Row) {
108 | let slotIndex = convert(indexInNew: indexInNew)
109 | slots[slotIndex] = previousRow.slots[slotIndex - 1]
110 | }
111 |
112 | /// Choose the min
113 | func updateWithMin(previousRow: Row, indexInNew: Int, newItem: T, indexInOld: Int, oldItem: T) {
114 | let slotIndex = convert(indexInNew: indexInNew)
115 | let topSlot = previousRow.slots[slotIndex]
116 | let leftSlot = slots[slotIndex - 1]
117 | let topLeftSlot = previousRow.slots[slotIndex - 1]
118 |
119 | let minCount = min(topSlot.count, leftSlot.count, topLeftSlot.count)
120 |
121 | // Order of cases does not matter
122 | switch minCount {
123 | case topSlot.count:
124 | slots[slotIndex] = combine(
125 | slot: topSlot,
126 | change: .delete(Delete(item: oldItem, index: indexInOld))
127 | )
128 | case leftSlot.count:
129 | slots[slotIndex] = combine(
130 | slot: leftSlot,
131 | change: .insert(Insert(item: newItem, index: indexInNew))
132 | )
133 | case topLeftSlot.count:
134 | slots[slotIndex] = combine(
135 | slot: topLeftSlot,
136 | change: .replace(Replace(oldItem: oldItem, newItem: newItem, index: indexInNew))
137 | )
138 | default:
139 | assertionFailure()
140 | }
141 | }
142 |
143 | /// Add one more change
144 | func combine(slot: [Change], change: Change) -> [Change] {
145 | var slot = slot
146 | slot.append(change)
147 | return slot
148 | }
149 |
150 | //// Last slot
151 | func lastSlot() -> [Change] {
152 | return slots[slots.count - 1]
153 | }
154 |
155 | /// Convert to slotIndex, as slots has 1 extra at the beginning
156 | func convert(indexInNew: Int) -> Int {
157 | return indexInNew + 1
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/Sources/Shared/Array+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+Extensions.swift
3 | // DeepDiff
4 | //
5 | // Created by Khoa Pham.
6 | // Copyright © 2018 Khoa Pham. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Array {
12 | func executeIfPresent(_ closure: ([Element]) -> Void) {
13 | if !isEmpty {
14 | closure(self)
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/Shared/Change.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Change.swift
3 | // DeepDiff
4 | //
5 | // Created by Khoa Pham.
6 | // Copyright © 2018 Khoa Pham. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Insert {
12 | public let item: T
13 | public let index: Int
14 | }
15 |
16 | public struct Delete {
17 | public let item: T
18 | public let index: Int
19 | }
20 |
21 | public struct Replace {
22 | public let oldItem: T
23 | public let newItem: T
24 | public let index: Int
25 | }
26 |
27 | public struct Move {
28 | public let item: T
29 | public let fromIndex: Int
30 | public let toIndex: Int
31 | }
32 |
33 | /// The computed changes from diff
34 | ///
35 | /// - insert: Insert an item at index
36 | /// - delete: Delete an item from index
37 | /// - replace: Replace an item at index with another item
38 | /// - move: Move the same item from this index to another index
39 | public enum Change {
40 | case insert(Insert)
41 | case delete(Delete)
42 | case replace(Replace)
43 | case move(Move)
44 |
45 | public var insert: Insert? {
46 | if case .insert(let insert) = self {
47 | return insert
48 | }
49 |
50 | return nil
51 | }
52 |
53 | public var delete: Delete? {
54 | if case .delete(let delete) = self {
55 | return delete
56 | }
57 |
58 | return nil
59 | }
60 |
61 | public var replace: Replace? {
62 | if case .replace(let replace) = self {
63 | return replace
64 | }
65 |
66 | return nil
67 | }
68 |
69 | public var move: Move? {
70 | if case .move(let move) = self {
71 | return move
72 | }
73 |
74 | return nil
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Sources/Shared/DeepDiff.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DeepDiff.swift
3 | // DeepDiff
4 | //
5 | // Created by Khoa Pham.
6 | // Copyright © 2018 Khoa Pham. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Perform diff between old and new collections
12 | ///
13 | /// - Parameters:
14 | /// - old: Old collection
15 | /// - new: New collection
16 | /// - Returns: A set of changes
17 |
18 | public func diff(old: [T], new: [T]) -> [Change] {
19 | let heckel = Heckel()
20 | return heckel.diff(old: old, new: new)
21 | }
22 |
23 | public func preprocess(old: [T], new: [T]) -> [Change]? {
24 | switch (old.isEmpty, new.isEmpty) {
25 | case (true, true):
26 | // empty
27 | return []
28 | case (true, false):
29 | // all .insert
30 | return new.enumerated().map { index, item in
31 | return .insert(Insert(item: item, index: index))
32 | }
33 | case (false, true):
34 | // all .delete
35 | return old.enumerated().map { index, item in
36 | return .delete(Delete(item: item, index: index))
37 | }
38 | default:
39 | return nil
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Sources/Shared/DiffAware.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DiffAware.swift
3 | // DeepDiff
4 | //
5 | // Created by khoa on 22/02/2019.
6 | // Copyright © 2019 Khoa Pham. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Model must conform to DiffAware for diffing to work properly
12 | /// diffId: Each object must be uniquely identified by id. This is to tell if there is deletion or insertion
13 | /// compareContent: An object can change some properties but having its id intact. This is to tell if there is replacement
14 | public protocol DiffAware {
15 | associatedtype DiffId: Hashable
16 |
17 | var diffId: DiffId { get }
18 | static func compareContent(_ a: Self, _ b: Self) -> Bool
19 | }
20 |
21 | public extension DiffAware where Self: Hashable {
22 | var diffId: Self {
23 | return self
24 | }
25 |
26 | static func compareContent(_ a: Self, _ b: Self) -> Bool {
27 | return a == b
28 | }
29 | }
30 |
31 | extension Int: DiffAware {}
32 | extension String: DiffAware {}
33 | extension Character: DiffAware {}
34 | extension UUID: DiffAware {}
35 |
36 |
--------------------------------------------------------------------------------
/Sources/Shared/MoveReducer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MoveReducer.swift
3 | // DeepDiff
4 | //
5 | // Created by Khoa Pham.
6 | // Copyright © 2018 Khoa Pham. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct MoveReducer {
12 | func reduce(changes: [Change]) -> [Change] {
13 | let compareContentWithOptional: (T?, T) -> Bool = { a, b in
14 | guard let a = a else {
15 | return false
16 | }
17 |
18 | return T.compareContent(a, b)
19 | }
20 |
21 | // Find pairs of .insert and .delete with same item
22 | let inserts = changes.compactMap({ $0.insert })
23 |
24 | if inserts.isEmpty {
25 | return changes
26 | }
27 |
28 | var changes = changes
29 | inserts.forEach { insert in
30 | if let insertIndex = changes.firstIndex(where: { return compareContentWithOptional($0.insert?.item, insert.item) }),
31 | let deleteIndex = changes.firstIndex(where: { return compareContentWithOptional($0.delete?.item, insert.item) }) {
32 |
33 | let insertChange = changes[insertIndex].insert!
34 | let deleteChange = changes[deleteIndex].delete!
35 |
36 | let move = Move(item: insert.item, fromIndex: deleteChange.index, toIndex: insertChange.index)
37 |
38 | // .insert can be before or after .delete
39 | let minIndex = min(insertIndex, deleteIndex)
40 | let maxIndex = max(insertIndex, deleteIndex)
41 |
42 | // remove both .insert and .delete, and replace by .move
43 | changes.remove(at: minIndex)
44 | changes.remove(at: maxIndex.advanced(by: -1))
45 | changes.insert(.move(move), at: minIndex)
46 | }
47 | }
48 |
49 | return changes
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/iOS/IndexPathConverter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IndexPathConverter.swift
3 | // DeepDiff
4 | //
5 | // Created by Khoa Pham.
6 | // Copyright © 2018 Khoa Pham. All rights reserved.
7 | //
8 |
9 | #if os(iOS) || os(tvOS)
10 | import Foundation
11 |
12 | public struct ChangeWithIndexPath {
13 |
14 | public let inserts: [IndexPath]
15 | public let deletes: [IndexPath]
16 | public let replaces: [IndexPath]
17 | public let moves: [(from: IndexPath, to: IndexPath)]
18 |
19 | public init(
20 | inserts: [IndexPath],
21 | deletes: [IndexPath],
22 | replaces:[IndexPath],
23 | moves: [(from: IndexPath, to: IndexPath)]) {
24 |
25 | self.inserts = inserts
26 | self.deletes = deletes
27 | self.replaces = replaces
28 | self.moves = moves
29 | }
30 | }
31 |
32 | public class IndexPathConverter {
33 |
34 | public init() {}
35 |
36 | public func convert(changes: [Change], section: Int) -> ChangeWithIndexPath {
37 | let inserts = changes.compactMap({ $0.insert }).map({ $0.index.toIndexPath(section: section) })
38 | let deletes = changes.compactMap({ $0.delete }).map({ $0.index.toIndexPath(section: section) })
39 | let replaces = changes.compactMap({ $0.replace }).map({ $0.index.toIndexPath(section: section) })
40 | let moves = changes.compactMap({ $0.move }).map({
41 | (
42 | from: $0.fromIndex.toIndexPath(section: section),
43 | to: $0.toIndex.toIndexPath(section: section)
44 | )
45 | })
46 |
47 | return ChangeWithIndexPath(
48 | inserts: inserts,
49 | deletes: deletes,
50 | replaces: replaces,
51 | moves: moves
52 | )
53 | }
54 | }
55 |
56 | extension Int {
57 |
58 | fileprivate func toIndexPath(section: Int) -> IndexPath {
59 | return IndexPath(item: self, section: section)
60 | }
61 | }
62 | #endif
63 |
--------------------------------------------------------------------------------
/Sources/iOS/UICollectionView+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionView+Extensions.swift
3 | // DeepDiff
4 | //
5 | // Created by Khoa Pham.
6 | // Copyright © 2018 Khoa Pham. All rights reserved.
7 | //
8 |
9 | #if os(iOS) || os(tvOS)
10 | import UIKit
11 |
12 | public extension UICollectionView {
13 |
14 | /// Animate reload in a batch update
15 | ///
16 | /// - Parameters:
17 | /// - changes: The changes from diff
18 | /// - section: The section that all calculated IndexPath belong
19 | /// - updateData: Update your data source model
20 | /// - completion: Called when operation completes
21 | func reload(
22 | changes: [Change],
23 | section: Int = 0,
24 | updateData: () -> Void,
25 | completion: ((Bool) -> Void)? = nil) {
26 |
27 | let changesWithIndexPath = IndexPathConverter().convert(changes: changes, section: section)
28 |
29 | performBatchUpdates({
30 | updateData()
31 | insideUpdate(changesWithIndexPath: changesWithIndexPath)
32 | }, completion: { finished in
33 | completion?(finished)
34 | })
35 |
36 | // reloadRows needs to be called outside the batch
37 | outsideUpdate(changesWithIndexPath: changesWithIndexPath)
38 | }
39 |
40 | // MARK: - Helper
41 |
42 | private func insideUpdate(changesWithIndexPath: ChangeWithIndexPath) {
43 | changesWithIndexPath.deletes.executeIfPresent {
44 | deleteItems(at: $0)
45 | }
46 |
47 | changesWithIndexPath.inserts.executeIfPresent {
48 | insertItems(at: $0)
49 | }
50 |
51 | changesWithIndexPath.moves.executeIfPresent {
52 | $0.forEach { move in
53 | moveItem(at: move.from, to: move.to)
54 | }
55 | }
56 | }
57 |
58 | private func outsideUpdate(changesWithIndexPath: ChangeWithIndexPath) {
59 | changesWithIndexPath.replaces.executeIfPresent {
60 | self.reloadItems(at: $0)
61 | }
62 | }
63 | }
64 | #endif
65 |
--------------------------------------------------------------------------------
/Sources/iOS/UITableView+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITableView+Extensions.swift
3 | // DeepDiff
4 | //
5 | // Created by Khoa Pham.
6 | // Copyright © 2018 Khoa Pham. All rights reserved.
7 | //
8 |
9 | #if os(iOS) || os(tvOS)
10 | import UIKit
11 |
12 | public extension UITableView {
13 |
14 | /// Animate reload in a batch update
15 | ///
16 | /// - Parameters:
17 | /// - changes: The changes from diff
18 | /// - section: The section that all calculated IndexPath belong
19 | /// - insertionAnimation: The animation for insert rows
20 | /// - deletionAnimation: The animation for delete rows
21 | /// - replacementAnimation: The animation for reload rows
22 | /// - updateData: Update your data source model
23 | /// - completion: Called when operation completes
24 | func reload(
25 | changes: [Change],
26 | section: Int = 0,
27 | insertionAnimation: UITableView.RowAnimation = .automatic,
28 | deletionAnimation: UITableView.RowAnimation = .automatic,
29 | replacementAnimation: UITableView.RowAnimation = .automatic,
30 | updateData: () -> Void,
31 | completion: ((Bool) -> Void)? = nil) {
32 |
33 | let changesWithIndexPath = IndexPathConverter().convert(changes: changes, section: section)
34 |
35 | unifiedPerformBatchUpdates({
36 | updateData()
37 | self.insideUpdate(
38 | changesWithIndexPath: changesWithIndexPath,
39 | insertionAnimation: insertionAnimation,
40 | deletionAnimation: deletionAnimation
41 | )
42 | }, completion: { finished in
43 | completion?(finished)
44 | })
45 |
46 | // reloadRows needs to be called outside the batch
47 | outsideUpdate(changesWithIndexPath: changesWithIndexPath, replacementAnimation: replacementAnimation)
48 | }
49 |
50 | // MARK: - Helper
51 |
52 | private func unifiedPerformBatchUpdates(
53 | _ updates: (() -> Void),
54 | completion: (@escaping (Bool) -> Void)) {
55 |
56 | if #available(iOS 11, tvOS 11, *) {
57 | performBatchUpdates(updates, completion: completion)
58 | } else {
59 | beginUpdates()
60 | updates()
61 | endUpdates()
62 | completion(true)
63 | }
64 | }
65 |
66 | private func insideUpdate(
67 | changesWithIndexPath: ChangeWithIndexPath,
68 | insertionAnimation: UITableView.RowAnimation,
69 | deletionAnimation: UITableView.RowAnimation) {
70 |
71 | changesWithIndexPath.deletes.executeIfPresent {
72 | deleteRows(at: $0, with: deletionAnimation)
73 | }
74 |
75 | changesWithIndexPath.inserts.executeIfPresent {
76 | insertRows(at: $0, with: insertionAnimation)
77 | }
78 |
79 | changesWithIndexPath.moves.executeIfPresent {
80 | $0.forEach { move in
81 | moveRow(at: move.from, to: move.to)
82 | }
83 | }
84 | }
85 |
86 | private func outsideUpdate(
87 | changesWithIndexPath: ChangeWithIndexPath,
88 | replacementAnimation: UITableView.RowAnimation) {
89 |
90 | changesWithIndexPath.replaces.executeIfPresent {
91 | reloadRows(at: $0, with: replacementAnimation)
92 | }
93 | }
94 | }
95 | #endif
96 |
--------------------------------------------------------------------------------