├── .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 | [![CI Status](https://img.shields.io/circleci/project/github/onmyway133/DeepDiff.svg)](https://circleci.com/gh/onmyway133/DeepDiff) 15 | [![Version](https://img.shields.io/cocoapods/v/DeepDiff.svg?style=flat)](http://cocoadocs.org/docsets/DeepDiff) 16 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 17 | [![License](https://img.shields.io/cocoapods/l/DeepDiff.svg?style=flat)](http://cocoadocs.org/docsets/DeepDiff) 18 | [![Platform](https://img.shields.io/cocoapods/p/DeepDiff.svg?style=flat)](http://cocoadocs.org/docsets/DeepDiff) 19 | ![Swift](https://img.shields.io/badge/%20in-swift%204.0-orange.svg) 20 | 21 | ![](Screenshots/Banner.png) 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 | ![](Screenshots/benchmark3d.png) 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 | --------------------------------------------------------------------------------