├── .gitignore ├── .travis.yml ├── CoreDataMigration-Example.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── CoreDataMigration-Example.xcscheme ├── CoreDataMigration-Example ├── Application │ ├── AppDelegate.swift │ └── Info.plist ├── CoreData │ ├── CoreDataManager.swift │ ├── Migration │ │ ├── CoreDataMigrationStep.swift │ │ ├── CoreDataMigrationVersion.swift │ │ ├── CoreDataMigrator.swift │ │ ├── Mappings │ │ │ └── Migration2to3ModelMapping.xcmappingmodel │ │ │ │ └── xcmapping.xml │ │ └── Policies │ │ │ └── Post2ToPost3MigrationPolicy.swift │ └── Model │ │ └── CoreDataMigration_Example.xcdatamodeld │ │ ├── .xccurrentversion │ │ ├── CoreDataMigration_Example 2.xcdatamodel │ │ └── contents │ │ ├── CoreDataMigration_Example 3.xcdatamodel │ │ └── contents │ │ ├── CoreDataMigration_Example 4.xcdatamodel │ │ └── contents │ │ └── CoreDataMigration_Example.xcdatamodel │ │ └── contents ├── Extensions │ ├── FileManager │ │ └── FileManager+ApplicationSupport.swift │ ├── NSManagedObjectModel │ │ ├── NSManagedObjectModel+Compatible.swift │ │ └── NSManagedObjectModel+Resource.swift │ ├── NSPersistentStoreCoordinator │ │ └── NSPersistentStoreCoordinator+SQLite.swift │ └── UIColor │ │ ├── UIColor+Hex.swift │ │ └── UIColor+Random.swift ├── Resources │ └── Assets.xcassets │ │ └── AppIcon.appiconset │ │ └── Contents.json ├── Storyboards │ ├── AppLoading.storyboard │ └── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard └── ViewControllers │ ├── Loading │ └── AppLoadingViewController.swift │ ├── Posts │ ├── PostTableViewCell.swift │ └── PostsViewController.swift │ ├── Viewer │ ├── PostSectionViewerTableViewCell.swift │ └── PostViewerViewController.swift │ └── Writer │ ├── PostSectionWriterTableViewCell.swift │ └── PostWriterViewController.swift ├── CoreDataMigration-ExampleTests ├── Helpers │ ├── FileManager+Helper.swift │ └── NSManagedObjectContext+Helper.swift ├── Supporting Files │ └── Info.plist └── Tests │ ├── CoreData │ ├── Manager │ │ └── CoreDataManagerTests.swift │ └── Migration │ │ ├── CoreDataMigratorTests.swift │ │ └── Models │ │ ├── CoreDataMigration_Example_1.sqlite │ │ ├── CoreDataMigration_Example_2.sqlite │ │ └── CoreDataMigration_Example_3.sqlite │ └── Mocks │ └── MockCoreDataMigrator.swift ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md └── fastlane ├── Fastfile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | cache: 4 | - bundler 5 | 6 | osx_image: xcode10.1 7 | 8 | before_install: 9 | - bundle install 10 | 11 | script: 12 | - bundle exec fastlane run_unit_tests --verbose 13 | -------------------------------------------------------------------------------- /CoreDataMigration-Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3D8E52F521E0EF2800FE1D35 /* FileManager+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D8E52F421E0EF2800FE1D35 /* FileManager+Helper.swift */; }; 11 | 3D8E52F721E0F98500FE1D35 /* NSManagedObjectContext+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D8E52F621E0F98500FE1D35 /* NSManagedObjectContext+Helper.swift */; }; 12 | 3DACAAD221EE9D5D00309A75 /* PostSectionWriterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DACAAD121EE9D5D00309A75 /* PostSectionWriterTableViewCell.swift */; }; 13 | 3DD99ED021F1118700CB4B6E /* Migration2to3ModelMapping.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = 3DD99ECF21F1118700CB4B6E /* Migration2to3ModelMapping.xcmappingmodel */; }; 14 | 3DD99ED321F145EC00CB4B6E /* FileManager+ApplicationSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD99ED221F145EC00CB4B6E /* FileManager+ApplicationSupport.swift */; }; 15 | 3DDB26C921EBF87E00388AEE /* PostWriterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDB26C821EBF87E00388AEE /* PostWriterViewController.swift */; }; 16 | 3DDB26CB21EC00FE00388AEE /* PostViewerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDB26CA21EC00FE00388AEE /* PostViewerViewController.swift */; }; 17 | 431DCEAE1F67EC9E00CF6316 /* CoreDataMigration_Example.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 431DCEAA1F67EC7100CF6316 /* CoreDataMigration_Example.xcdatamodeld */; }; 18 | 431DCEBF1F67F18100CF6316 /* CoreDataMigrationStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431DCEB11F67EE2600CF6316 /* CoreDataMigrationStep.swift */; }; 19 | 431DCEC01F67F18100CF6316 /* CoreDataMigrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431DCEB21F67EE2600CF6316 /* CoreDataMigrator.swift */; }; 20 | 431DCEC51F67F80B00CF6316 /* AppLoading.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 431DCEC41F67F80B00CF6316 /* AppLoading.storyboard */; }; 21 | 431DCECB1F67F93000CF6316 /* AppLoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431DCECA1F67F93000CF6316 /* AppLoadingViewController.swift */; }; 22 | 431DCECC1F67FE0500CF6316 /* PostsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431DCEC91F67F91C00CF6316 /* PostsViewController.swift */; }; 23 | 431DCECD1F67FE0800CF6316 /* PostTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431DCEC81F67F91C00CF6316 /* PostTableViewCell.swift */; }; 24 | 431DCED21F6815A300CF6316 /* Post2ToPost3MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431DCED11F6815A300CF6316 /* Post2ToPost3MigrationPolicy.swift */; }; 25 | 432EA5591F6C552800EFE008 /* NSPersistentStoreCoordinator+SQLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 432EA5581F6C552800EFE008 /* NSPersistentStoreCoordinator+SQLite.swift */; }; 26 | 43370DB01F66E7A6006188EC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43370D751F66E74A006188EC /* AppDelegate.swift */; }; 27 | 43370DB31F66E830006188EC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43370D781F66E74A006188EC /* LaunchScreen.storyboard */; }; 28 | 43370DB41F66E832006188EC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43370D7A1F66E74A006188EC /* Main.storyboard */; }; 29 | 43370DBE1F66F0DF006188EC /* CoreDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43370DBB1F66F0C0006188EC /* CoreDataManager.swift */; }; 30 | 4345D4EE1F67E0FE00027D11 /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345D4ED1F67E0FE00027D11 /* UIColor+Hex.swift */; }; 31 | 4345D4F01F67E10700027D11 /* UIColor+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345D4EF1F67E10700027D11 /* UIColor+Random.swift */; }; 32 | C23BD45A21F08A350039A36B /* PostSectionViewerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23BD45921F08A350039A36B /* PostSectionViewerTableViewCell.swift */; }; 33 | C28553DF21DCF5000004C7BA /* CoreDataMigrationVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28553DE21DCF5000004C7BA /* CoreDataMigrationVersion.swift */; }; 34 | C28553E221DD14090004C7BA /* NSManagedObjectModel+Compatible.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28553E121DD14090004C7BA /* NSManagedObjectModel+Compatible.swift */; }; 35 | C28553E421DD1D7B0004C7BA /* NSManagedObjectModel+Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28553E321DD1D7B0004C7BA /* NSManagedObjectModel+Resource.swift */; }; 36 | C28553F821DD21C40004C7BA /* CoreDataManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28553EE21DD21C30004C7BA /* CoreDataManagerTests.swift */; }; 37 | C28553FB21DD21C40004C7BA /* CoreDataMigration_Example_2.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = C28553F321DD21C30004C7BA /* CoreDataMigration_Example_2.sqlite */; }; 38 | C28553FD21DD21C40004C7BA /* CoreDataMigration_Example_1.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = C28553F521DD21C30004C7BA /* CoreDataMigration_Example_1.sqlite */; }; 39 | C28553FE21DD21C40004C7BA /* CoreDataMigration_Example_3.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = C28553F621DD21C30004C7BA /* CoreDataMigration_Example_3.sqlite */; }; 40 | C28553FF21DD21C40004C7BA /* CoreDataMigratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28553F721DD21C30004C7BA /* CoreDataMigratorTests.swift */; }; 41 | C285540221DD22040004C7BA /* MockCoreDataMigrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C285540121DD22040004C7BA /* MockCoreDataMigrator.swift */; }; 42 | /* End PBXBuildFile section */ 43 | 44 | /* Begin PBXContainerItemProxy section */ 45 | 43AB8AFD1F66E6A5003153B3 /* PBXContainerItemProxy */ = { 46 | isa = PBXContainerItemProxy; 47 | containerPortal = 43AB8ADD1F66E6A5003153B3 /* Project object */; 48 | proxyType = 1; 49 | remoteGlobalIDString = 43AB8AE41F66E6A5003153B3; 50 | remoteInfo = "CoreDataMigration-Example"; 51 | }; 52 | /* End PBXContainerItemProxy section */ 53 | 54 | /* Begin PBXFileReference section */ 55 | 3D8E52F421E0EF2800FE1D35 /* FileManager+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Helper.swift"; sourceTree = ""; }; 56 | 3D8E52F621E0F98500FE1D35 /* NSManagedObjectContext+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Helper.swift"; sourceTree = ""; }; 57 | 3DACAAD121EE9D5D00309A75 /* PostSectionWriterTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostSectionWriterTableViewCell.swift; sourceTree = ""; }; 58 | 3DD99ECF21F1118700CB4B6E /* Migration2to3ModelMapping.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = Migration2to3ModelMapping.xcmappingmodel; sourceTree = ""; }; 59 | 3DD99ED221F145EC00CB4B6E /* FileManager+ApplicationSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+ApplicationSupport.swift"; sourceTree = ""; }; 60 | 3DDB26C821EBF87E00388AEE /* PostWriterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostWriterViewController.swift; sourceTree = ""; }; 61 | 3DDB26CA21EC00FE00388AEE /* PostViewerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewerViewController.swift; sourceTree = ""; }; 62 | 431DCEAB1F67EC7100CF6316 /* CoreDataMigration_Example 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "CoreDataMigration_Example 2.xcdatamodel"; sourceTree = ""; }; 63 | 431DCEAC1F67EC7100CF6316 /* CoreDataMigration_Example 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "CoreDataMigration_Example 3.xcdatamodel"; sourceTree = ""; }; 64 | 431DCEAD1F67EC7100CF6316 /* CoreDataMigration_Example.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = CoreDataMigration_Example.xcdatamodel; sourceTree = ""; }; 65 | 431DCEB11F67EE2600CF6316 /* CoreDataMigrationStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataMigrationStep.swift; sourceTree = ""; }; 66 | 431DCEB21F67EE2600CF6316 /* CoreDataMigrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataMigrator.swift; sourceTree = ""; }; 67 | 431DCEC41F67F80B00CF6316 /* AppLoading.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = AppLoading.storyboard; sourceTree = ""; }; 68 | 431DCEC81F67F91C00CF6316 /* PostTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTableViewCell.swift; sourceTree = ""; }; 69 | 431DCEC91F67F91C00CF6316 /* PostsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostsViewController.swift; sourceTree = ""; }; 70 | 431DCECA1F67F93000CF6316 /* AppLoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLoadingViewController.swift; sourceTree = ""; }; 71 | 431DCED11F6815A300CF6316 /* Post2ToPost3MigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post2ToPost3MigrationPolicy.swift; sourceTree = ""; }; 72 | 431DCED71F68394200CF6316 /* CoreDataMigration_Example 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "CoreDataMigration_Example 4.xcdatamodel"; sourceTree = ""; }; 73 | 432EA5581F6C552800EFE008 /* NSPersistentStoreCoordinator+SQLite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSPersistentStoreCoordinator+SQLite.swift"; sourceTree = ""; }; 74 | 43370D731F66E74A006188EC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 75 | 43370D751F66E74A006188EC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 76 | 43370D761F66E74A006188EC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 77 | 43370D791F66E74A006188EC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 78 | 43370D7B1F66E74A006188EC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 79 | 43370DBB1F66F0C0006188EC /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; }; 80 | 4345D4ED1F67E0FE00027D11 /* UIColor+Hex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Hex.swift"; sourceTree = ""; }; 81 | 4345D4EF1F67E10700027D11 /* UIColor+Random.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Random.swift"; sourceTree = ""; }; 82 | 43AB8AE51F66E6A5003153B3 /* CoreDataMigration-Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "CoreDataMigration-Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 83 | 43AB8AFC1F66E6A5003153B3 /* CoreDataMigration-ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CoreDataMigration-ExampleTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 84 | C23BD45921F08A350039A36B /* PostSectionViewerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostSectionViewerTableViewCell.swift; sourceTree = ""; }; 85 | C28553DE21DCF5000004C7BA /* CoreDataMigrationVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataMigrationVersion.swift; sourceTree = ""; }; 86 | C28553E121DD14090004C7BA /* NSManagedObjectModel+Compatible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectModel+Compatible.swift"; sourceTree = ""; }; 87 | C28553E321DD1D7B0004C7BA /* NSManagedObjectModel+Resource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectModel+Resource.swift"; sourceTree = ""; }; 88 | C28553E821DD21950004C7BA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 89 | C28553EE21DD21C30004C7BA /* CoreDataManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataManagerTests.swift; sourceTree = ""; }; 90 | C28553F321DD21C30004C7BA /* CoreDataMigration_Example_2.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = CoreDataMigration_Example_2.sqlite; sourceTree = ""; }; 91 | C28553F521DD21C30004C7BA /* CoreDataMigration_Example_1.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = CoreDataMigration_Example_1.sqlite; sourceTree = ""; }; 92 | C28553F621DD21C30004C7BA /* CoreDataMigration_Example_3.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = CoreDataMigration_Example_3.sqlite; sourceTree = ""; }; 93 | C28553F721DD21C30004C7BA /* CoreDataMigratorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataMigratorTests.swift; sourceTree = ""; }; 94 | C285540121DD22040004C7BA /* MockCoreDataMigrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCoreDataMigrator.swift; sourceTree = ""; }; 95 | /* End PBXFileReference section */ 96 | 97 | /* Begin PBXFrameworksBuildPhase section */ 98 | 43AB8AE21F66E6A5003153B3 /* Frameworks */ = { 99 | isa = PBXFrameworksBuildPhase; 100 | buildActionMask = 2147483647; 101 | files = ( 102 | ); 103 | runOnlyForDeploymentPostprocessing = 0; 104 | }; 105 | 43AB8AF91F66E6A5003153B3 /* Frameworks */ = { 106 | isa = PBXFrameworksBuildPhase; 107 | buildActionMask = 2147483647; 108 | files = ( 109 | ); 110 | runOnlyForDeploymentPostprocessing = 0; 111 | }; 112 | /* End PBXFrameworksBuildPhase section */ 113 | 114 | /* Begin PBXGroup section */ 115 | 3D2C746521DFFE5600514491 /* Helpers */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 3D8E52F421E0EF2800FE1D35 /* FileManager+Helper.swift */, 119 | 3D8E52F621E0F98500FE1D35 /* NSManagedObjectContext+Helper.swift */, 120 | ); 121 | path = Helpers; 122 | sourceTree = ""; 123 | }; 124 | 3DD99ED121F145CF00CB4B6E /* FileManager */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 3DD99ED221F145EC00CB4B6E /* FileManager+ApplicationSupport.swift */, 128 | ); 129 | path = FileManager; 130 | sourceTree = ""; 131 | }; 132 | 3DDB26C621EBF86300388AEE /* Viewer */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 3DDB26CA21EC00FE00388AEE /* PostViewerViewController.swift */, 136 | C23BD45921F08A350039A36B /* PostSectionViewerTableViewCell.swift */, 137 | ); 138 | path = Viewer; 139 | sourceTree = ""; 140 | }; 141 | 3DDB26C721EBF86300388AEE /* Writer */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 3DDB26C821EBF87E00388AEE /* PostWriterViewController.swift */, 145 | 3DACAAD121EE9D5D00309A75 /* PostSectionWriterTableViewCell.swift */, 146 | ); 147 | path = Writer; 148 | sourceTree = ""; 149 | }; 150 | 431DCEAF1F67EE2600CF6316 /* Migration */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 431DCEB11F67EE2600CF6316 /* CoreDataMigrationStep.swift */, 154 | C28553DE21DCF5000004C7BA /* CoreDataMigrationVersion.swift */, 155 | 431DCEB21F67EE2600CF6316 /* CoreDataMigrator.swift */, 156 | 431DCEB31F67EE2600CF6316 /* Mappings */, 157 | 431DCED01F68156B00CF6316 /* Policies */, 158 | ); 159 | path = Migration; 160 | sourceTree = ""; 161 | }; 162 | 431DCEB31F67EE2600CF6316 /* Mappings */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 3DD99ECF21F1118700CB4B6E /* Migration2to3ModelMapping.xcmappingmodel */, 166 | ); 167 | path = Mappings; 168 | sourceTree = ""; 169 | }; 170 | 431DCEBC1F67F07100CF6316 /* NSPersistentStoreCoordinator */ = { 171 | isa = PBXGroup; 172 | children = ( 173 | 432EA5581F6C552800EFE008 /* NSPersistentStoreCoordinator+SQLite.swift */, 174 | ); 175 | path = NSPersistentStoreCoordinator; 176 | sourceTree = ""; 177 | }; 178 | 431DCEC61F67F91C00CF6316 /* Loading */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | 431DCECA1F67F93000CF6316 /* AppLoadingViewController.swift */, 182 | ); 183 | path = Loading; 184 | sourceTree = ""; 185 | }; 186 | 431DCEC71F67F91C00CF6316 /* Posts */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | 431DCEC81F67F91C00CF6316 /* PostTableViewCell.swift */, 190 | 431DCEC91F67F91C00CF6316 /* PostsViewController.swift */, 191 | ); 192 | path = Posts; 193 | sourceTree = ""; 194 | }; 195 | 431DCED01F68156B00CF6316 /* Policies */ = { 196 | isa = PBXGroup; 197 | children = ( 198 | 431DCED11F6815A300CF6316 /* Post2ToPost3MigrationPolicy.swift */, 199 | ); 200 | path = Policies; 201 | sourceTree = ""; 202 | }; 203 | 43370D721F66E74A006188EC /* Resources */ = { 204 | isa = PBXGroup; 205 | children = ( 206 | 43370D731F66E74A006188EC /* Assets.xcassets */, 207 | ); 208 | path = Resources; 209 | sourceTree = ""; 210 | }; 211 | 43370D741F66E74A006188EC /* Application */ = { 212 | isa = PBXGroup; 213 | children = ( 214 | 43370D751F66E74A006188EC /* AppDelegate.swift */, 215 | 43370D761F66E74A006188EC /* Info.plist */, 216 | ); 217 | path = Application; 218 | sourceTree = ""; 219 | }; 220 | 43370D771F66E74A006188EC /* Storyboards */ = { 221 | isa = PBXGroup; 222 | children = ( 223 | 431DCEC41F67F80B00CF6316 /* AppLoading.storyboard */, 224 | 43370D781F66E74A006188EC /* LaunchScreen.storyboard */, 225 | 43370D7A1F66E74A006188EC /* Main.storyboard */, 226 | ); 227 | path = Storyboards; 228 | sourceTree = ""; 229 | }; 230 | 43370D7C1F66E74A006188EC /* ViewControllers */ = { 231 | isa = PBXGroup; 232 | children = ( 233 | 431DCEC61F67F91C00CF6316 /* Loading */, 234 | 431DCEC71F67F91C00CF6316 /* Posts */, 235 | 3DDB26C621EBF86300388AEE /* Viewer */, 236 | 3DDB26C721EBF86300388AEE /* Writer */, 237 | ); 238 | path = ViewControllers; 239 | sourceTree = ""; 240 | }; 241 | 43370D7E1F66E74A006188EC /* CoreData */ = { 242 | isa = PBXGroup; 243 | children = ( 244 | 43370DBB1F66F0C0006188EC /* CoreDataManager.swift */, 245 | 431DCEAF1F67EE2600CF6316 /* Migration */, 246 | 43370DB91F66F0C0006188EC /* Model */, 247 | ); 248 | path = CoreData; 249 | sourceTree = ""; 250 | }; 251 | 43370DB91F66F0C0006188EC /* Model */ = { 252 | isa = PBXGroup; 253 | children = ( 254 | 431DCEAA1F67EC7100CF6316 /* CoreDataMigration_Example.xcdatamodeld */, 255 | ); 256 | path = Model; 257 | sourceTree = ""; 258 | }; 259 | 4345D4EA1F67E0DD00027D11 /* Extensions */ = { 260 | isa = PBXGroup; 261 | children = ( 262 | 3DD99ED121F145CF00CB4B6E /* FileManager */, 263 | C28553E021DD13EF0004C7BA /* NSManagedObjectModel */, 264 | 431DCEBC1F67F07100CF6316 /* NSPersistentStoreCoordinator */, 265 | 4345D4EC1F67E0DD00027D11 /* UIColor */, 266 | ); 267 | path = Extensions; 268 | sourceTree = ""; 269 | }; 270 | 4345D4EC1F67E0DD00027D11 /* UIColor */ = { 271 | isa = PBXGroup; 272 | children = ( 273 | 4345D4ED1F67E0FE00027D11 /* UIColor+Hex.swift */, 274 | 4345D4EF1F67E10700027D11 /* UIColor+Random.swift */, 275 | ); 276 | path = UIColor; 277 | sourceTree = ""; 278 | }; 279 | 43AB8ADC1F66E6A5003153B3 = { 280 | isa = PBXGroup; 281 | children = ( 282 | 43AB8AE71F66E6A5003153B3 /* CoreDataMigration-Example */, 283 | 43AB8AFF1F66E6A5003153B3 /* CoreDataMigration-ExampleTests */, 284 | 43AB8AE61F66E6A5003153B3 /* Products */, 285 | ); 286 | sourceTree = ""; 287 | }; 288 | 43AB8AE61F66E6A5003153B3 /* Products */ = { 289 | isa = PBXGroup; 290 | children = ( 291 | 43AB8AE51F66E6A5003153B3 /* CoreDataMigration-Example.app */, 292 | 43AB8AFC1F66E6A5003153B3 /* CoreDataMigration-ExampleTests.xctest */, 293 | ); 294 | name = Products; 295 | sourceTree = ""; 296 | }; 297 | 43AB8AE71F66E6A5003153B3 /* CoreDataMigration-Example */ = { 298 | isa = PBXGroup; 299 | children = ( 300 | 43370D741F66E74A006188EC /* Application */, 301 | 43370D7E1F66E74A006188EC /* CoreData */, 302 | 4345D4EA1F67E0DD00027D11 /* Extensions */, 303 | 43370D721F66E74A006188EC /* Resources */, 304 | 43370D771F66E74A006188EC /* Storyboards */, 305 | 43370D7C1F66E74A006188EC /* ViewControllers */, 306 | ); 307 | path = "CoreDataMigration-Example"; 308 | sourceTree = ""; 309 | }; 310 | 43AB8AFF1F66E6A5003153B3 /* CoreDataMigration-ExampleTests */ = { 311 | isa = PBXGroup; 312 | children = ( 313 | 3D2C746521DFFE5600514491 /* Helpers */, 314 | C28553E521DD21950004C7BA /* Supporting Files */, 315 | C28553EB21DD21C30004C7BA /* Tests */, 316 | ); 317 | path = "CoreDataMigration-ExampleTests"; 318 | sourceTree = ""; 319 | }; 320 | C28553E021DD13EF0004C7BA /* NSManagedObjectModel */ = { 321 | isa = PBXGroup; 322 | children = ( 323 | C28553E121DD14090004C7BA /* NSManagedObjectModel+Compatible.swift */, 324 | C28553E321DD1D7B0004C7BA /* NSManagedObjectModel+Resource.swift */, 325 | ); 326 | path = NSManagedObjectModel; 327 | sourceTree = ""; 328 | }; 329 | C28553E521DD21950004C7BA /* Supporting Files */ = { 330 | isa = PBXGroup; 331 | children = ( 332 | C28553E821DD21950004C7BA /* Info.plist */, 333 | ); 334 | path = "Supporting Files"; 335 | sourceTree = ""; 336 | }; 337 | C28553EB21DD21C30004C7BA /* Tests */ = { 338 | isa = PBXGroup; 339 | children = ( 340 | C285540021DD21F50004C7BA /* Mocks */, 341 | C28553EC21DD21C30004C7BA /* CoreData */, 342 | ); 343 | path = Tests; 344 | sourceTree = ""; 345 | }; 346 | C28553EC21DD21C30004C7BA /* CoreData */ = { 347 | isa = PBXGroup; 348 | children = ( 349 | C28553ED21DD21C30004C7BA /* Manager */, 350 | C28553EF21DD21C30004C7BA /* Migration */, 351 | ); 352 | path = CoreData; 353 | sourceTree = ""; 354 | }; 355 | C28553ED21DD21C30004C7BA /* Manager */ = { 356 | isa = PBXGroup; 357 | children = ( 358 | C28553EE21DD21C30004C7BA /* CoreDataManagerTests.swift */, 359 | ); 360 | path = Manager; 361 | sourceTree = ""; 362 | }; 363 | C28553EF21DD21C30004C7BA /* Migration */ = { 364 | isa = PBXGroup; 365 | children = ( 366 | C28553F721DD21C30004C7BA /* CoreDataMigratorTests.swift */, 367 | C28553F121DD21C30004C7BA /* Models */, 368 | ); 369 | path = Migration; 370 | sourceTree = ""; 371 | }; 372 | C28553F121DD21C30004C7BA /* Models */ = { 373 | isa = PBXGroup; 374 | children = ( 375 | C28553F521DD21C30004C7BA /* CoreDataMigration_Example_1.sqlite */, 376 | C28553F321DD21C30004C7BA /* CoreDataMigration_Example_2.sqlite */, 377 | C28553F621DD21C30004C7BA /* CoreDataMigration_Example_3.sqlite */, 378 | ); 379 | path = Models; 380 | sourceTree = ""; 381 | }; 382 | C285540021DD21F50004C7BA /* Mocks */ = { 383 | isa = PBXGroup; 384 | children = ( 385 | C285540121DD22040004C7BA /* MockCoreDataMigrator.swift */, 386 | ); 387 | path = Mocks; 388 | sourceTree = ""; 389 | }; 390 | /* End PBXGroup section */ 391 | 392 | /* Begin PBXNativeTarget section */ 393 | 43AB8AE41F66E6A5003153B3 /* CoreDataMigration-Example */ = { 394 | isa = PBXNativeTarget; 395 | buildConfigurationList = 43AB8B051F66E6A5003153B3 /* Build configuration list for PBXNativeTarget "CoreDataMigration-Example" */; 396 | buildPhases = ( 397 | 43AB8AE11F66E6A5003153B3 /* Sources */, 398 | 43AB8AE21F66E6A5003153B3 /* Frameworks */, 399 | 43AB8AE31F66E6A5003153B3 /* Resources */, 400 | ); 401 | buildRules = ( 402 | ); 403 | dependencies = ( 404 | ); 405 | name = "CoreDataMigration-Example"; 406 | productName = "CoreDataMigration-Example"; 407 | productReference = 43AB8AE51F66E6A5003153B3 /* CoreDataMigration-Example.app */; 408 | productType = "com.apple.product-type.application"; 409 | }; 410 | 43AB8AFB1F66E6A5003153B3 /* CoreDataMigration-ExampleTests */ = { 411 | isa = PBXNativeTarget; 412 | buildConfigurationList = 43AB8B081F66E6A5003153B3 /* Build configuration list for PBXNativeTarget "CoreDataMigration-ExampleTests" */; 413 | buildPhases = ( 414 | 43AB8AF81F66E6A5003153B3 /* Sources */, 415 | 43AB8AF91F66E6A5003153B3 /* Frameworks */, 416 | 43AB8AFA1F66E6A5003153B3 /* Resources */, 417 | ); 418 | buildRules = ( 419 | ); 420 | dependencies = ( 421 | 43AB8AFE1F66E6A5003153B3 /* PBXTargetDependency */, 422 | ); 423 | name = "CoreDataMigration-ExampleTests"; 424 | productName = "CoreDataMigration-ExampleTests"; 425 | productReference = 43AB8AFC1F66E6A5003153B3 /* CoreDataMigration-ExampleTests.xctest */; 426 | productType = "com.apple.product-type.bundle.unit-test"; 427 | }; 428 | /* End PBXNativeTarget section */ 429 | 430 | /* Begin PBXProject section */ 431 | 43AB8ADD1F66E6A5003153B3 /* Project object */ = { 432 | isa = PBXProject; 433 | attributes = { 434 | LastSwiftUpdateCheck = 0900; 435 | LastUpgradeCheck = 1010; 436 | ORGANIZATIONNAME = "William Boles"; 437 | TargetAttributes = { 438 | 43AB8AE41F66E6A5003153B3 = { 439 | CreatedOnToolsVersion = 9.0; 440 | LastSwiftMigration = 1010; 441 | ProvisioningStyle = Automatic; 442 | }; 443 | 43AB8AFB1F66E6A5003153B3 = { 444 | CreatedOnToolsVersion = 9.0; 445 | LastSwiftMigration = 1010; 446 | ProvisioningStyle = Automatic; 447 | TestTargetID = 43AB8AE41F66E6A5003153B3; 448 | }; 449 | }; 450 | }; 451 | buildConfigurationList = 43AB8AE01F66E6A5003153B3 /* Build configuration list for PBXProject "CoreDataMigration-Example" */; 452 | compatibilityVersion = "Xcode 8.0"; 453 | developmentRegion = en; 454 | hasScannedForEncodings = 0; 455 | knownRegions = ( 456 | en, 457 | Base, 458 | ); 459 | mainGroup = 43AB8ADC1F66E6A5003153B3; 460 | productRefGroup = 43AB8AE61F66E6A5003153B3 /* Products */; 461 | projectDirPath = ""; 462 | projectRoot = ""; 463 | targets = ( 464 | 43AB8AE41F66E6A5003153B3 /* CoreDataMigration-Example */, 465 | 43AB8AFB1F66E6A5003153B3 /* CoreDataMigration-ExampleTests */, 466 | ); 467 | }; 468 | /* End PBXProject section */ 469 | 470 | /* Begin PBXResourcesBuildPhase section */ 471 | 43AB8AE31F66E6A5003153B3 /* Resources */ = { 472 | isa = PBXResourcesBuildPhase; 473 | buildActionMask = 2147483647; 474 | files = ( 475 | 431DCEC51F67F80B00CF6316 /* AppLoading.storyboard in Resources */, 476 | 43370DB31F66E830006188EC /* LaunchScreen.storyboard in Resources */, 477 | 43370DB41F66E832006188EC /* Main.storyboard in Resources */, 478 | ); 479 | runOnlyForDeploymentPostprocessing = 0; 480 | }; 481 | 43AB8AFA1F66E6A5003153B3 /* Resources */ = { 482 | isa = PBXResourcesBuildPhase; 483 | buildActionMask = 2147483647; 484 | files = ( 485 | C28553FE21DD21C40004C7BA /* CoreDataMigration_Example_3.sqlite in Resources */, 486 | C28553FB21DD21C40004C7BA /* CoreDataMigration_Example_2.sqlite in Resources */, 487 | C28553FD21DD21C40004C7BA /* CoreDataMigration_Example_1.sqlite in Resources */, 488 | ); 489 | runOnlyForDeploymentPostprocessing = 0; 490 | }; 491 | /* End PBXResourcesBuildPhase section */ 492 | 493 | /* Begin PBXSourcesBuildPhase section */ 494 | 43AB8AE11F66E6A5003153B3 /* Sources */ = { 495 | isa = PBXSourcesBuildPhase; 496 | buildActionMask = 2147483647; 497 | files = ( 498 | 43370DB01F66E7A6006188EC /* AppDelegate.swift in Sources */, 499 | 431DCEC01F67F18100CF6316 /* CoreDataMigrator.swift in Sources */, 500 | C28553E421DD1D7B0004C7BA /* NSManagedObjectModel+Resource.swift in Sources */, 501 | 431DCECB1F67F93000CF6316 /* AppLoadingViewController.swift in Sources */, 502 | 3DDB26CB21EC00FE00388AEE /* PostViewerViewController.swift in Sources */, 503 | 3DD99ED021F1118700CB4B6E /* Migration2to3ModelMapping.xcmappingmodel in Sources */, 504 | C28553E221DD14090004C7BA /* NSManagedObjectModel+Compatible.swift in Sources */, 505 | C28553DF21DCF5000004C7BA /* CoreDataMigrationVersion.swift in Sources */, 506 | 432EA5591F6C552800EFE008 /* NSPersistentStoreCoordinator+SQLite.swift in Sources */, 507 | C23BD45A21F08A350039A36B /* PostSectionViewerTableViewCell.swift in Sources */, 508 | 4345D4F01F67E10700027D11 /* UIColor+Random.swift in Sources */, 509 | 4345D4EE1F67E0FE00027D11 /* UIColor+Hex.swift in Sources */, 510 | 431DCED21F6815A300CF6316 /* Post2ToPost3MigrationPolicy.swift in Sources */, 511 | 431DCECD1F67FE0800CF6316 /* PostTableViewCell.swift in Sources */, 512 | 431DCECC1F67FE0500CF6316 /* PostsViewController.swift in Sources */, 513 | 43370DBE1F66F0DF006188EC /* CoreDataManager.swift in Sources */, 514 | 3DD99ED321F145EC00CB4B6E /* FileManager+ApplicationSupport.swift in Sources */, 515 | 431DCEBF1F67F18100CF6316 /* CoreDataMigrationStep.swift in Sources */, 516 | 431DCEAE1F67EC9E00CF6316 /* CoreDataMigration_Example.xcdatamodeld in Sources */, 517 | 3DDB26C921EBF87E00388AEE /* PostWriterViewController.swift in Sources */, 518 | 3DACAAD221EE9D5D00309A75 /* PostSectionWriterTableViewCell.swift in Sources */, 519 | ); 520 | runOnlyForDeploymentPostprocessing = 0; 521 | }; 522 | 43AB8AF81F66E6A5003153B3 /* Sources */ = { 523 | isa = PBXSourcesBuildPhase; 524 | buildActionMask = 2147483647; 525 | files = ( 526 | C285540221DD22040004C7BA /* MockCoreDataMigrator.swift in Sources */, 527 | 3D8E52F721E0F98500FE1D35 /* NSManagedObjectContext+Helper.swift in Sources */, 528 | C28553F821DD21C40004C7BA /* CoreDataManagerTests.swift in Sources */, 529 | C28553FF21DD21C40004C7BA /* CoreDataMigratorTests.swift in Sources */, 530 | 3D8E52F521E0EF2800FE1D35 /* FileManager+Helper.swift in Sources */, 531 | ); 532 | runOnlyForDeploymentPostprocessing = 0; 533 | }; 534 | /* End PBXSourcesBuildPhase section */ 535 | 536 | /* Begin PBXTargetDependency section */ 537 | 43AB8AFE1F66E6A5003153B3 /* PBXTargetDependency */ = { 538 | isa = PBXTargetDependency; 539 | target = 43AB8AE41F66E6A5003153B3 /* CoreDataMigration-Example */; 540 | targetProxy = 43AB8AFD1F66E6A5003153B3 /* PBXContainerItemProxy */; 541 | }; 542 | /* End PBXTargetDependency section */ 543 | 544 | /* Begin PBXVariantGroup section */ 545 | 43370D781F66E74A006188EC /* LaunchScreen.storyboard */ = { 546 | isa = PBXVariantGroup; 547 | children = ( 548 | 43370D791F66E74A006188EC /* Base */, 549 | ); 550 | name = LaunchScreen.storyboard; 551 | sourceTree = ""; 552 | }; 553 | 43370D7A1F66E74A006188EC /* Main.storyboard */ = { 554 | isa = PBXVariantGroup; 555 | children = ( 556 | 43370D7B1F66E74A006188EC /* Base */, 557 | ); 558 | name = Main.storyboard; 559 | sourceTree = ""; 560 | }; 561 | /* End PBXVariantGroup section */ 562 | 563 | /* Begin XCBuildConfiguration section */ 564 | 43AB8B031F66E6A5003153B3 /* Debug */ = { 565 | isa = XCBuildConfiguration; 566 | buildSettings = { 567 | ALWAYS_SEARCH_USER_PATHS = NO; 568 | CLANG_ANALYZER_NONNULL = YES; 569 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 570 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 571 | CLANG_CXX_LIBRARY = "libc++"; 572 | CLANG_ENABLE_MODULES = YES; 573 | CLANG_ENABLE_OBJC_ARC = YES; 574 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 575 | CLANG_WARN_BOOL_CONVERSION = YES; 576 | CLANG_WARN_COMMA = YES; 577 | CLANG_WARN_CONSTANT_CONVERSION = YES; 578 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 579 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 580 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 581 | CLANG_WARN_EMPTY_BODY = YES; 582 | CLANG_WARN_ENUM_CONVERSION = YES; 583 | CLANG_WARN_INFINITE_RECURSION = YES; 584 | CLANG_WARN_INT_CONVERSION = YES; 585 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 586 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 587 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 588 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 589 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 590 | CLANG_WARN_STRICT_PROTOTYPES = YES; 591 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 592 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 593 | CLANG_WARN_UNREACHABLE_CODE = YES; 594 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 595 | CODE_SIGN_IDENTITY = "iPhone Developer"; 596 | COPY_PHASE_STRIP = NO; 597 | DEBUG_INFORMATION_FORMAT = dwarf; 598 | ENABLE_STRICT_OBJC_MSGSEND = YES; 599 | ENABLE_TESTABILITY = YES; 600 | GCC_C_LANGUAGE_STANDARD = gnu11; 601 | GCC_DYNAMIC_NO_PIC = NO; 602 | GCC_NO_COMMON_BLOCKS = YES; 603 | GCC_OPTIMIZATION_LEVEL = 0; 604 | GCC_PREPROCESSOR_DEFINITIONS = ( 605 | "DEBUG=1", 606 | "$(inherited)", 607 | ); 608 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 609 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 610 | GCC_WARN_UNDECLARED_SELECTOR = YES; 611 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 612 | GCC_WARN_UNUSED_FUNCTION = YES; 613 | GCC_WARN_UNUSED_VARIABLE = YES; 614 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 615 | MTL_ENABLE_DEBUG_INFO = YES; 616 | ONLY_ACTIVE_ARCH = YES; 617 | SDKROOT = iphoneos; 618 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 619 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 620 | }; 621 | name = Debug; 622 | }; 623 | 43AB8B041F66E6A5003153B3 /* Release */ = { 624 | isa = XCBuildConfiguration; 625 | buildSettings = { 626 | ALWAYS_SEARCH_USER_PATHS = NO; 627 | CLANG_ANALYZER_NONNULL = YES; 628 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 629 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 630 | CLANG_CXX_LIBRARY = "libc++"; 631 | CLANG_ENABLE_MODULES = YES; 632 | CLANG_ENABLE_OBJC_ARC = YES; 633 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 634 | CLANG_WARN_BOOL_CONVERSION = YES; 635 | CLANG_WARN_COMMA = YES; 636 | CLANG_WARN_CONSTANT_CONVERSION = YES; 637 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 638 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 639 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 640 | CLANG_WARN_EMPTY_BODY = YES; 641 | CLANG_WARN_ENUM_CONVERSION = YES; 642 | CLANG_WARN_INFINITE_RECURSION = YES; 643 | CLANG_WARN_INT_CONVERSION = YES; 644 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 645 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 646 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 647 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 648 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 649 | CLANG_WARN_STRICT_PROTOTYPES = YES; 650 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 651 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 652 | CLANG_WARN_UNREACHABLE_CODE = YES; 653 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 654 | CODE_SIGN_IDENTITY = "iPhone Developer"; 655 | COPY_PHASE_STRIP = NO; 656 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 657 | ENABLE_NS_ASSERTIONS = NO; 658 | ENABLE_STRICT_OBJC_MSGSEND = YES; 659 | GCC_C_LANGUAGE_STANDARD = gnu11; 660 | GCC_NO_COMMON_BLOCKS = YES; 661 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 662 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 663 | GCC_WARN_UNDECLARED_SELECTOR = YES; 664 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 665 | GCC_WARN_UNUSED_FUNCTION = YES; 666 | GCC_WARN_UNUSED_VARIABLE = YES; 667 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 668 | MTL_ENABLE_DEBUG_INFO = NO; 669 | SDKROOT = iphoneos; 670 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 671 | VALIDATE_PRODUCT = YES; 672 | }; 673 | name = Release; 674 | }; 675 | 43AB8B061F66E6A5003153B3 /* Debug */ = { 676 | isa = XCBuildConfiguration; 677 | buildSettings = { 678 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 679 | CODE_SIGN_STYLE = Automatic; 680 | INFOPLIST_FILE = "CoreDataMigration-Example/Application/Info.plist"; 681 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 682 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 683 | PRODUCT_BUNDLE_IDENTIFIER = "com.williamboles.CoreDataMigration-Example"; 684 | PRODUCT_NAME = "$(TARGET_NAME)"; 685 | SWIFT_VERSION = 4.2; 686 | TARGETED_DEVICE_FAMILY = "1,2"; 687 | }; 688 | name = Debug; 689 | }; 690 | 43AB8B071F66E6A5003153B3 /* Release */ = { 691 | isa = XCBuildConfiguration; 692 | buildSettings = { 693 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 694 | CODE_SIGN_STYLE = Automatic; 695 | INFOPLIST_FILE = "CoreDataMigration-Example/Application/Info.plist"; 696 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 697 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 698 | PRODUCT_BUNDLE_IDENTIFIER = "com.williamboles.CoreDataMigration-Example"; 699 | PRODUCT_NAME = "$(TARGET_NAME)"; 700 | SWIFT_VERSION = 4.2; 701 | TARGETED_DEVICE_FAMILY = "1,2"; 702 | }; 703 | name = Release; 704 | }; 705 | 43AB8B091F66E6A5003153B3 /* Debug */ = { 706 | isa = XCBuildConfiguration; 707 | buildSettings = { 708 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 709 | BUNDLE_LOADER = "$(TEST_HOST)"; 710 | CLANG_ENABLE_MODULES = YES; 711 | CODE_SIGN_STYLE = Automatic; 712 | INFOPLIST_FILE = "CoreDataMigration-ExampleTests/Supporting Files/Info.plist"; 713 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 714 | PRODUCT_BUNDLE_IDENTIFIER = "com.williamboles.CoreDataMigration-ExampleTests"; 715 | PRODUCT_NAME = "$(TARGET_NAME)"; 716 | SWIFT_OBJC_BRIDGING_HEADER = ""; 717 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 718 | SWIFT_VERSION = 4.2; 719 | TARGETED_DEVICE_FAMILY = "1,2"; 720 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CoreDataMigration-Example.app/CoreDataMigration-Example"; 721 | }; 722 | name = Debug; 723 | }; 724 | 43AB8B0A1F66E6A5003153B3 /* Release */ = { 725 | isa = XCBuildConfiguration; 726 | buildSettings = { 727 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 728 | BUNDLE_LOADER = "$(TEST_HOST)"; 729 | CLANG_ENABLE_MODULES = YES; 730 | CODE_SIGN_STYLE = Automatic; 731 | INFOPLIST_FILE = "CoreDataMigration-ExampleTests/Supporting Files/Info.plist"; 732 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 733 | PRODUCT_BUNDLE_IDENTIFIER = "com.williamboles.CoreDataMigration-ExampleTests"; 734 | PRODUCT_NAME = "$(TARGET_NAME)"; 735 | SWIFT_OBJC_BRIDGING_HEADER = ""; 736 | SWIFT_VERSION = 4.2; 737 | TARGETED_DEVICE_FAMILY = "1,2"; 738 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CoreDataMigration-Example.app/CoreDataMigration-Example"; 739 | }; 740 | name = Release; 741 | }; 742 | /* End XCBuildConfiguration section */ 743 | 744 | /* Begin XCConfigurationList section */ 745 | 43AB8AE01F66E6A5003153B3 /* Build configuration list for PBXProject "CoreDataMigration-Example" */ = { 746 | isa = XCConfigurationList; 747 | buildConfigurations = ( 748 | 43AB8B031F66E6A5003153B3 /* Debug */, 749 | 43AB8B041F66E6A5003153B3 /* Release */, 750 | ); 751 | defaultConfigurationIsVisible = 0; 752 | defaultConfigurationName = Release; 753 | }; 754 | 43AB8B051F66E6A5003153B3 /* Build configuration list for PBXNativeTarget "CoreDataMigration-Example" */ = { 755 | isa = XCConfigurationList; 756 | buildConfigurations = ( 757 | 43AB8B061F66E6A5003153B3 /* Debug */, 758 | 43AB8B071F66E6A5003153B3 /* Release */, 759 | ); 760 | defaultConfigurationIsVisible = 0; 761 | defaultConfigurationName = Release; 762 | }; 763 | 43AB8B081F66E6A5003153B3 /* Build configuration list for PBXNativeTarget "CoreDataMigration-ExampleTests" */ = { 764 | isa = XCConfigurationList; 765 | buildConfigurations = ( 766 | 43AB8B091F66E6A5003153B3 /* Debug */, 767 | 43AB8B0A1F66E6A5003153B3 /* Release */, 768 | ); 769 | defaultConfigurationIsVisible = 0; 770 | defaultConfigurationName = Release; 771 | }; 772 | /* End XCConfigurationList section */ 773 | 774 | /* Begin XCVersionGroup section */ 775 | 431DCEAA1F67EC7100CF6316 /* CoreDataMigration_Example.xcdatamodeld */ = { 776 | isa = XCVersionGroup; 777 | children = ( 778 | 431DCED71F68394200CF6316 /* CoreDataMigration_Example 4.xcdatamodel */, 779 | 431DCEAB1F67EC7100CF6316 /* CoreDataMigration_Example 2.xcdatamodel */, 780 | 431DCEAC1F67EC7100CF6316 /* CoreDataMigration_Example 3.xcdatamodel */, 781 | 431DCEAD1F67EC7100CF6316 /* CoreDataMigration_Example.xcdatamodel */, 782 | ); 783 | currentVersion = 431DCED71F68394200CF6316 /* CoreDataMigration_Example 4.xcdatamodel */; 784 | path = CoreDataMigration_Example.xcdatamodeld; 785 | sourceTree = ""; 786 | versionGroupType = wrapper.xcdatamodel; 787 | }; 788 | /* End XCVersionGroup section */ 789 | }; 790 | rootObject = 43AB8ADD1F66E6A5003153B3 /* Project object */; 791 | } 792 | -------------------------------------------------------------------------------- /CoreDataMigration-Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CoreDataMigration-Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CoreDataMigration-Example.xcodeproj/xcshareddata/xcschemes/CoreDataMigration-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 71 | 73 | 79 | 80 | 81 | 82 | 83 | 84 | 90 | 92 | 98 | 99 | 100 | 101 | 103 | 104 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/Application/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 11/09/2017. 6 | // Copyright © 2017 William Boles. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | // MARK: - AppLifecycle 18 | 19 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 20 | guard ProcessInfo.processInfo.environment["runningTests"] == nil else { 21 | FileManager.clearApplicationSupportDirectoryContents() 22 | return true 23 | } 24 | 25 | CoreDataManager.shared.setup { 26 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { // just for example purposes 27 | self.presentMainUI() 28 | } 29 | } 30 | 31 | return true 32 | } 33 | 34 | func applicationWillResignActive(_ application: UIApplication) { 35 | // 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. 36 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 37 | } 38 | 39 | func applicationDidEnterBackground(_ application: UIApplication) { 40 | // 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. 41 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 42 | } 43 | 44 | func applicationWillEnterForeground(_ application: UIApplication) { 45 | // 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. 46 | } 47 | 48 | func applicationDidBecomeActive(_ application: UIApplication) { 49 | // 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. 50 | } 51 | 52 | func applicationWillTerminate(_ application: UIApplication) { 53 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 54 | // Saves changes in the application's managed object context before the application terminates. 55 | 56 | try? CoreDataManager.shared.mainContext.save() 57 | } 58 | 59 | // MARK: - Main 60 | 61 | func presentMainUI() { 62 | let mainStoryboard = UIStoryboard(name: "Main", bundle: nil) 63 | window?.rootViewController = mainStoryboard.instantiateInitialViewController() 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/Application/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 | AppLoading 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/CoreData/CoreDataManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataManager.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 11/09/2017. 6 | // Copyright © 2017 William Boles. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | class CoreDataManager { 13 | 14 | let migrator: CoreDataMigratorProtocol 15 | private let storeType: String 16 | 17 | lazy var persistentContainer: NSPersistentContainer = { 18 | let persistentContainer = NSPersistentContainer(name: "CoreDataMigration_Example") 19 | let description = persistentContainer.persistentStoreDescriptions.first 20 | description?.shouldInferMappingModelAutomatically = false //inferred mapping will be handled else where 21 | description?.shouldMigrateStoreAutomatically = false 22 | description?.type = storeType 23 | 24 | return persistentContainer 25 | }() 26 | 27 | lazy var backgroundContext: NSManagedObjectContext = { 28 | let context = self.persistentContainer.newBackgroundContext() 29 | context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy 30 | 31 | return context 32 | }() 33 | 34 | lazy var mainContext: NSManagedObjectContext = { 35 | let context = self.persistentContainer.viewContext 36 | context.automaticallyMergesChangesFromParent = true 37 | 38 | return context 39 | }() 40 | 41 | // MARK: - Singleton 42 | 43 | static let shared = CoreDataManager() 44 | 45 | // MARK: - Init 46 | 47 | init(storeType: String = NSSQLiteStoreType, migrator: CoreDataMigratorProtocol = CoreDataMigrator()) { 48 | self.storeType = storeType 49 | self.migrator = migrator 50 | } 51 | 52 | // MARK: - SetUp 53 | 54 | func setup(completion: @escaping () -> Void) { 55 | loadPersistentStore { 56 | completion() 57 | } 58 | } 59 | 60 | // MARK: - Loading 61 | 62 | private func loadPersistentStore(completion: @escaping () -> Void) { 63 | migrateStoreIfNeeded { 64 | self.persistentContainer.loadPersistentStores { description, error in 65 | guard error == nil else { 66 | fatalError("was unable to load store \(error!)") 67 | } 68 | 69 | completion() 70 | } 71 | } 72 | } 73 | 74 | private func migrateStoreIfNeeded(completion: @escaping () -> Void) { 75 | guard let storeURL = persistentContainer.persistentStoreDescriptions.first?.url else { 76 | fatalError("persistentContainer was not set up properly") 77 | } 78 | 79 | if migrator.requiresMigration(at: storeURL, toVersion: CoreDataMigrationVersion.current) { 80 | DispatchQueue.global(qos: .userInitiated).async { 81 | self.migrator.migrateStore(at: storeURL, toVersion: CoreDataMigrationVersion.current) 82 | 83 | DispatchQueue.main.async { 84 | completion() 85 | } 86 | } 87 | } else { 88 | completion() 89 | } 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/CoreData/Migration/CoreDataMigrationStep.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataMigrationStep.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 11/09/2017. 6 | // Copyright © 2017 William Boles. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | struct CoreDataMigrationStep { 12 | 13 | let sourceModel: NSManagedObjectModel 14 | let destinationModel: NSManagedObjectModel 15 | let mappingModel: NSMappingModel 16 | 17 | // MARK: Init 18 | 19 | init(sourceVersion: CoreDataMigrationVersion, destinationVersion: CoreDataMigrationVersion) { 20 | let sourceModel = NSManagedObjectModel.managedObjectModel(forResource: sourceVersion.rawValue) 21 | let destinationModel = NSManagedObjectModel.managedObjectModel(forResource: destinationVersion.rawValue) 22 | 23 | guard let mappingModel = CoreDataMigrationStep.mappingModel(fromSourceModel: sourceModel, toDestinationModel: destinationModel) else { 24 | fatalError("Expected modal mapping not present") 25 | } 26 | 27 | self.sourceModel = sourceModel 28 | self.destinationModel = destinationModel 29 | self.mappingModel = mappingModel 30 | } 31 | 32 | // MARK: - Mapping 33 | 34 | private static func mappingModel(fromSourceModel sourceModel: NSManagedObjectModel, toDestinationModel destinationModel: NSManagedObjectModel) -> NSMappingModel? { 35 | guard let customMapping = customMappingModel(fromSourceModel: sourceModel, toDestinationModel: destinationModel) else { 36 | return inferredMappingModel(fromSourceModel:sourceModel, toDestinationModel: destinationModel) 37 | } 38 | 39 | return customMapping 40 | } 41 | 42 | private static func inferredMappingModel(fromSourceModel sourceModel: NSManagedObjectModel, toDestinationModel destinationModel: NSManagedObjectModel) -> NSMappingModel? { 43 | return try? NSMappingModel.inferredMappingModel(forSourceModel: sourceModel, destinationModel: destinationModel) 44 | } 45 | 46 | private static func customMappingModel(fromSourceModel sourceModel: NSManagedObjectModel, toDestinationModel destinationModel: NSManagedObjectModel) -> NSMappingModel? { 47 | return NSMappingModel(from: [Bundle.main], forSourceModel: sourceModel, destinationModel: destinationModel) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/CoreData/Migration/CoreDataMigrationVersion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataVersion.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 02/01/2019. 6 | // Copyright © 2019 William Boles. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | enum CoreDataMigrationVersion: String, CaseIterable { 13 | case version1 = "CoreDataMigration_Example" 14 | case version2 = "CoreDataMigration_Example 2" 15 | case version3 = "CoreDataMigration_Example 3" 16 | case version4 = "CoreDataMigration_Example 4" 17 | 18 | // MARK: - Current 19 | 20 | static var current: CoreDataMigrationVersion { 21 | guard let latest = allCases.last else { 22 | fatalError("no model versions found") 23 | } 24 | 25 | return latest 26 | } 27 | 28 | // MARK: - Migration 29 | 30 | func nextVersion() -> CoreDataMigrationVersion? { 31 | switch self { 32 | case .version1: 33 | return .version2 34 | case .version2: 35 | return .version3 36 | case .version3: 37 | return .version4 38 | case .version4: 39 | return nil 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/CoreData/Migration/CoreDataMigrator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataMigrator.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 11/09/2017. 6 | // Copyright © 2017 William Boles. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | protocol CoreDataMigratorProtocol { 12 | func requiresMigration(at storeURL: URL, toVersion version: CoreDataMigrationVersion) -> Bool 13 | func migrateStore(at storeURL: URL, toVersion version: CoreDataMigrationVersion) 14 | } 15 | 16 | /** 17 | Responsible for handling Core Data model migrations. 18 | 19 | The default Core Data model migration approach is to go from earlier version to all possible future versions. 20 | 21 | So, if we have 4 model versions (1, 2, 3, 4), you would need to create the following mappings 1 to 4, 2 to 4 and 3 to 4. 22 | Then when we create model version 5, we would create mappings 1 to 5, 2 to 5, 3 to 5 and 4 to 5. You can see that for each 23 | new version we must create new mappings from all previous versions to the current version. This does not scale well, in the 24 | above example 4 new mappings have been created. For each new version you must add n-1 new mappings. 25 | 26 | Instead the solution below uses an iterative approach where we migrate mutliple times through a chain of model versions. 27 | 28 | So, if we have 4 model versions (1, 2, 3, 4), you would need to create the following mappings 1 to 2, 2 to 3 and 3 to 4. 29 | Then when we create model version 5, we only need to create one additional mapping 4 to 5. This greatly reduces the work 30 | required when adding a new version. 31 | */ 32 | class CoreDataMigrator: CoreDataMigratorProtocol { 33 | 34 | // MARK: - Check 35 | 36 | func requiresMigration(at storeURL: URL, toVersion version: CoreDataMigrationVersion) -> Bool { 37 | guard let metadata = NSPersistentStoreCoordinator.metadata(at: storeURL) else { 38 | return false 39 | } 40 | 41 | return (CoreDataMigrationVersion.compatibleVersionForStoreMetadata(metadata) != version) 42 | } 43 | 44 | // MARK: - Migration 45 | 46 | func migrateStore(at storeURL: URL, toVersion version: CoreDataMigrationVersion) { 47 | forceWALCheckpointingForStore(at: storeURL) 48 | 49 | var currentURL = storeURL 50 | let migrationSteps = self.migrationStepsForStore(at: storeURL, toVersion: version) 51 | 52 | for migrationStep in migrationSteps { 53 | let manager = NSMigrationManager(sourceModel: migrationStep.sourceModel, destinationModel: migrationStep.destinationModel) 54 | let destinationURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(UUID().uuidString) 55 | 56 | do { 57 | try manager.migrateStore(from: currentURL, sourceType: NSSQLiteStoreType, options: nil, with: migrationStep.mappingModel, toDestinationURL: destinationURL, destinationType: NSSQLiteStoreType, destinationOptions: nil) 58 | } catch let error { 59 | fatalError("failed attempting to migrate from \(migrationStep.sourceModel) to \(migrationStep.destinationModel), error: \(error)") 60 | } 61 | 62 | if currentURL != storeURL { 63 | //Destroy intermediate step's store 64 | NSPersistentStoreCoordinator.destroyStore(at: currentURL) 65 | } 66 | 67 | currentURL = destinationURL 68 | } 69 | 70 | NSPersistentStoreCoordinator.replaceStore(at: storeURL, withStoreAt: currentURL) 71 | 72 | if (currentURL != storeURL) { 73 | NSPersistentStoreCoordinator.destroyStore(at: currentURL) 74 | } 75 | } 76 | 77 | private func migrationStepsForStore(at storeURL: URL, toVersion destinationVersion: CoreDataMigrationVersion) -> [CoreDataMigrationStep] { 78 | guard let metadata = NSPersistentStoreCoordinator.metadata(at: storeURL), let sourceVersion = CoreDataMigrationVersion.compatibleVersionForStoreMetadata(metadata) else { 79 | fatalError("unknown store version at URL \(storeURL)") 80 | } 81 | 82 | return migrationSteps(fromSourceVersion: sourceVersion, toDestinationVersion: destinationVersion) 83 | } 84 | 85 | private func migrationSteps(fromSourceVersion sourceVersion: CoreDataMigrationVersion, toDestinationVersion destinationVersion: CoreDataMigrationVersion) -> [CoreDataMigrationStep] { 86 | var sourceVersion = sourceVersion 87 | var migrationSteps = [CoreDataMigrationStep]() 88 | 89 | while sourceVersion != destinationVersion, let nextVersion = sourceVersion.nextVersion() { 90 | let migrationStep = CoreDataMigrationStep(sourceVersion: sourceVersion, destinationVersion: nextVersion) 91 | migrationSteps.append(migrationStep) 92 | 93 | sourceVersion = nextVersion 94 | } 95 | 96 | return migrationSteps 97 | } 98 | 99 | // MARK: - WAL 100 | 101 | func forceWALCheckpointingForStore(at storeURL: URL) { 102 | guard let metadata = NSPersistentStoreCoordinator.metadata(at: storeURL), let currentModel = NSManagedObjectModel.compatibleModelForStoreMetadata(metadata) else { 103 | return 104 | } 105 | 106 | do { 107 | let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: currentModel) 108 | 109 | let options = [NSSQLitePragmasOption: ["journal_mode": "DELETE"]] 110 | let store = persistentStoreCoordinator.addPersistentStore(at: storeURL, options: options) 111 | try persistentStoreCoordinator.remove(store) 112 | } catch let error { 113 | fatalError("failed to force WAL checkpointing, error: \(error)") 114 | } 115 | } 116 | } 117 | 118 | private extension CoreDataMigrationVersion { 119 | 120 | // MARK: - Compatible 121 | 122 | static func compatibleVersionForStoreMetadata(_ metadata: [String : Any]) -> CoreDataMigrationVersion? { 123 | let compatibleVersion = CoreDataMigrationVersion.allCases.first { 124 | let model = NSManagedObjectModel.managedObjectModel(forResource: $0.rawValue) 125 | 126 | return model.isConfiguration(withName: nil, compatibleWithStoreMetadata: metadata) 127 | } 128 | 129 | return compatibleVersion 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/CoreData/Migration/Mappings/Migration2to3ModelMapping.xcmappingmodel/xcmapping.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 134481920 7 | 87E9047A-56E0-4CF2-BF13-03049CE47DD8 8 | 112 9 | 10 | 11 | 12 | NSPersistenceFrameworkVersion 13 | 866 14 | NSStoreModelVersionHashes 15 | 16 | XDDevAttributeMapping 17 | 18 | 0plcXXRN7XHKl5CcF+fwriFmUpON3ZtcI/AfK748aWc= 19 | 20 | XDDevEntityMapping 21 | 22 | qeN1Ym3TkWN1G6dU9RfX6Kd2ccEvcDVWHpd3LpLgboI= 23 | 24 | XDDevMappingModel 25 | 26 | EqtMzvRnVZWkXwBHu4VeVGy8UyoOe+bi67KC79kphlQ= 27 | 28 | XDDevPropertyMapping 29 | 30 | XN33V44TTGY4JETlMoOB5yyTKxB+u4slvDIinv0rtGA= 31 | 32 | XDDevRelationshipMapping 33 | 34 | akYY9LhehVA/mCb4ATLWuI9XGLcjpm14wWL1oEBtIcs= 35 | 36 | 37 | NSStoreModelVersionHashesVersion 38 | 3 39 | NSStoreModelVersionIdentifiers 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 1 49 | sections 50 | 51 | 52 | 53 | CoreDataMigration_Example.Post2ToPost3MigrationPolicy 54 | Post 55 | Undefined 56 | 2 57 | Post 58 | 1 59 | 60 | 61 | 62 | 63 | 64 | hexColor 65 | 66 | 67 | 68 | 1 69 | post 70 | 71 | 72 | 73 | body 74 | 75 | 76 | 77 | title 78 | 79 | 80 | 81 | date 82 | 83 | 84 | 85 | Undefined 86 | 1 87 | Section 88 | 1 89 | 90 | 91 | 92 | 93 | 94 | postID 95 | 96 | 97 | 98 | index 99 | 100 | 101 | 102 | CoreDataMigration-Example/CoreData/Model/CoreDataMigration_Example.xcdatamodeld/CoreDataMigration_Example 2.xcdatamodel 103 | YnBsaXN0MDDUAAEAAgADAAQABQAGBrMGtFgkdmVyc2lvblgkb2JqZWN0c1kkYXJjaGl2ZXJUJHRv 104 | cBIAAYagrxCuAAcACAAXADMANAA1AD0APgBZAFoAWwBhAGIAbgCEAIUAhgCHAIgAiQCKAIsAjACNAKYAqQCwALYAxQDUANcA5gD1APgAWAEIARcBGwEfAS4BNAE1AT0BTAFVAWEBYgFjAWQBZQF6AXsBgwGEAYUBkQGlAaYBpwGoAakBqgGrAawBrQG8AcsB2gHeAe0B/AH9AgwCGwIcAisCNwJJAkoCSwJMAk0CTgJPAlACXwJuAn0CjAKNApwCqwK6AsIC1wLYAuAC7AMAAw8DHgMtAzEDQANPA14DbQN8A4gDmgOpA7gDxwPWA+UD9AQDBBgEGQQhBC0EQQRQBF8EbgRyBIEEkASfBK4EvQTJBNsE6gT5BQgFFwUmBTUFRAVZBVoFYgVuBYIFkQWgBa8FswXCBdEF4AXvBf4GCgYcBisGOgZJBlgGWQZoBncGhgaHBooGkwaXBpsGnwanBqoGrgavVSRudWxs1wAJAAoACwAMAA0ADgAPABAAEQASABMAFAATABZfEA9feGRfcm9vdFBhY2thZ2VWJGNsYXNzXF94ZF9jb21tZW50c18QEF94ZF9tb2RlbE1hbmFnZXJfEBVfY29uZmlndXJhdGlvbnNCeU5hbWVdX3hkX21vZGVsTmFtZV8QF19tb2RlbFZlcnNpb25JZGVudGlmaWVygAKArYCqgACAq4AAgKzeABgAGQAaABsAHAAdAB4ACgAfACAAIQAiACMAJAAlACYAJwAoACUAEwArACwALQAuAC8AJQAlABNfEBxYREJ1Y2tldEZvckNsYXNzZXN3YXNFbmNvZGVkXxAaWERCdWNrZXRGb3JQYWNrYWdlc3N0b3JhZ2VfEBxYREJ1Y2tldEZvckludGVyZmFjZXNzdG9yYWdlXxAPX3hkX293bmluZ01vZGVsXxAdWERCdWNrZXRGb3JQYWNrYWdlc3dhc0VuY29kZWRWX293bmVyXxAbWERCdWNrZXRGb3JEYXRhVHlwZXNzdG9yYWdlW192aXNpYmlsaXR5XxAZWERCdWNrZXRGb3JDbGFzc2Vzc3RvcmFnZVVfbmFtZV8QH1hEQnVja2V0Rm9ySW50ZXJmYWNlc3dhc0VuY29kZWRfEB5YREJ1Y2tldEZvckRhdGFUeXBlc3dhc0VuY29kZWRfEBBfdW5pcXVlRWxlbWVudElEgASAqICmgAGABIAAgKeAqRAAgAWAA4AEgASAAFBTWUVT0wA2ADcACgA4ADoAPFdOUy5rZXlzWk5TLm9iamVjdHOhADmABqEAO4AHgCVUUG9zdN8QEAA/AEAAQQBCAB0AQwBEAB8ARQBGAAoAIQBHAEgAJABJAEoASwAlACUAEABPAFAALQAlAEoAUwA5AEoAVgBXAFhfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2VfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNkdXBsaWNhdGVzXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3N0b3JhZ2VbX2lzQWJzdHJhY3SACYAsgASABIACgAqAo4AEgAmApYAGgAmApIAICBJbJfVxV29yZGVyZWTTADYANwAKAFwAXgA8oQBdgAuhAF+ADIAlXlhEX1BTdGVyZW90eXBl2QAdACEAYwAKACQAZAAfAEkAZQA7AF0ASgBpABMAJQAtAFgAbV8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAHgAuACYArgACABAiADdMANgA3AAoAbwB5ADypAHAAcQByAHMAdAB1AHYAdwB4gA6AD4AQgBGAEoATgBSAFYAWqQB6AHsAfAB9AH4AfwCAAIEAgoAXgBuAHIAegB+AIYAjgCaAKoAlXxATWERQTUNvbXBvdW5kSW5kZXhlc18QEFhEX1BTS19lbGVtZW50SURfEBlYRFBNVW5p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3B3dHd8d4B4fHiEeIx4lHiceKB4pHioeKx4tHi8eMB4xHjMeNB5zHnUedx55HnsefB59Hn4efx6BHoMehB6FHoceiB7HHskeyx7NHs8e0B7RHtIe0x7VHtce2B7ZHtse3B8bHx0fHx8hHyMfJB8lHyYfJx8pHysfLB8tHy8fMB9vH3Efcx91H3cfeB95H3ofex99H38fgB+BH4MfhB/DH8Ufxx/JH8sfzB/NH84fzx/RH9Mf1B/VH9cf2CAjIEYgZiCGIIggiiCMII4gkCCRIJIglCCVIJcgmCCaIJwgnSCeIKAgoSCmILMguCC6ILwgwSDDIMUgxyDsIRAhNyFbIV0hXyFhIWMhZSFnIWghaiF3IYghiiGMIY4hkCGSIZQhliGYIakhqyGtIa8hsSGzIbUhtyG5Ibsh+iH8If4iACICIgMiBCIFIgYiCCIKIgsiDCIOIg8iTiJQIlIiVCJWIlciWCJZIloiXCJeIl8iYCJiImMioiKkIqYiqCKqIqsirCKtIq4isCKyIrMitCK2IrcixCLFIsYiyCMHIwkjCyMNIw8jECMRIxIjEyMVIxcjGCMZIxsjHCNbI10jXyNhI2MjZCNlI2YjZyNpI2sjbCNtI28jcCOvI7EjsyO1I7cjuCO5I7ojuyO9I78jwCPBI8MjxCQDJAUkByQJJAskDCQNJA4kDyQRJBMkFCQVJBckGCRXJFkkWyRdJF8kYCRhJGIkYyRlJGckaCRpJGskbCSRJLUk3CUAJQIlBCUGJQglCiUMJQ0lDyUcJSslLSUvJTElMyU1JTclOSVIJUolTCVOJVAlUiVUJVYlWCWXJZklmyWdJZ8loCWhJaIloyWlJaclqCWpJaslrCXrJe0l7yXxJfMl9CX1JfYl9yX5Jfsl/CX9Jf8mACY/JkEmQyZFJkcmSCZJJkomSyZNJk8mUCZRJlMmVCaTJpUmlyaZJpsmnCadJp4mnyahJqMmpCalJqcmqCbnJukm6ybtJu8m8CbxJvIm8yb1Jvcm+Cb5Jvsm/Cc7Jz0nPydBJ0MnRCdFJ0YnRydJJ0snTCdNJ08nUCePJ5EnkyeVJ5cnmCeZJ5onmyedJ58noCehJ6MnpCfvKBIoMihSKFQoVihYKFooXChdKF4oYChhKGMoZChmKGgoaShqKGwobShyKH8ohCiGKIgojSiPKJEokyi4KNwpAyknKSkpKyktKS8pMSkzKTQpNilDKVQpVilYKVopXCleKWApYilkKXUpdyl5KXspfSl/KYEpgymFKYcpxinIKcopzCnOKc8p0CnRKdIp1CnWKdcp2CnaKdsqGiocKh4qICoiKiMqJColKiYqKCoqKisqLCouKi8qbipwKnIqdCp2KncqeCp5KnoqfCp+Kn8qgCqCKoMqkCqRKpIqlCrTKtUq1yrZKtsq3CrdKt4q3yrhKuMq5CrlKucq6CsnKykrKystKy8rMCsxKzIrMys1KzcrOCs5KzsrPCt7K30rfyuBK4MrhCuFK4YrhyuJK4srjCuNK48rkCvPK9Er0yvVK9cr2CvZK9or2yvdK98r4CvhK+Mr5CwjLCUsJywpLCssLCwtLC4sLywxLDMsNCw1LDcsOCxdLIEsqCzMLM4s0CzSLNQs1izYLNks2yzoLPcs+Sz7LP0s/y0BLQMtBS0ULRYtGC0aLRwtHi0gLSItJC1jLWUtZy1pLWstbC1tLW4tby1xLXMtdC11LXcteC23Lbktuy29Lb8twC3BLcItwy3FLcctyC3JLcstzC4LLg0uDy4RLhMuFC4VLhYuFy4ZLhsuHC4dLh8uIC5fLmEuYy5lLmcuaC5pLmouay5tLm8ucC5xLnMudC53LrYuuC66Lrwuvi6/LsAuwS7CLsQuxi7HLsguyi7LLwovDC8OLxAvEi8TLxQvFS8WLxgvGi8bLxwvHi8fL14vYC9iL2QvZi9nL2gvaS9qL2wvbi9vL3Avci9zL34vhy+IL4ovky+eL60vuC/GL9sv7zAGMBgwJTAmMCcwKTA2MDcwODA6MEcwSDBJMEswVDBjMHAwfzCRMKUwvDDOMNcw2DDaMOcw6DDpMOsw7DD1MP8xBjEOMSAxJTEqAAAAAAAAAgIAAAAAAAAGtwAAAAAAAAAAAAAAAAAAMSw= 105 | 106 | CoreDataMigration-Example/CoreData/Model/CoreDataMigration_Example.xcdatamodeld/CoreDataMigration_Example 3.xcdatamodel 107 | YnBsaXN0MDDUAAEAAgADAAQABQAGDLcMuFgkdmVyc2lvblgkb2JqZWN0c1kkYXJjaGl2ZXJUJHRv 108 | cBIAAYagrxEBMAAHAAgAFwAzADQANQA/AEAAQQBcAF0AXgBkAGUAcQCHAIgAiQCKAIsAjACNAI4AjwCQAKkArACzALkAyADXANoA6QD4APsAWwELARoBHgEiATEBNwE4AUABTwFYAWQBZQFmAWcBaAF9AX4BhgGHAYgBlAGoAakBqgGrAawBrQGuAa8BsAG/Ac4B3QHhAfAB/wIAAg8CHgItAjkCSwJMAk0CTgJPAlACUQJSAmECcAJ/Ao4CjwKeAq0CvALEAtkC2gLiAuMC7wMDAxIDIQMwAzQDQwNSA2EDcAN/A4sDnQOeA58DoAOhA6IDowOkAC0DswPCA8MD0gPhA/sD/AQCBA4EJAQzBDYERQRUBFcEZgR1BHgEhwSWBJoEqQS4BMQExQTGBMcEyATdBN4E5gTyBQYFFQUkBTMFNwVGBVUFZAVzBYIFjgWgBa8FvgXNBdwF6wX6BgkGHgYfBicGMwZHBlYGZQZ0BngGhwaWBqUGtAbDBs8G4QbwBv8HDgcdBywHOwdKB18HYAdoB3QHiAeXB6YHtQe5B8gH1wfmB/UIBAgQCCIIMQhACEEIUAhfCG4IfQiMCJQIqQiqCLIIvgjSCOEI8Aj/CQMJEgkhCTAJPwlOCVoJbAl7CYoJmQmoCakJuAnHCdYJ1wnaCeMJ8goBChAKJQomCi4KOgpOCl0KbAp7Cn8KjgqdCqwKuwrKCtYK6Ar3CwYLFQskCzMLQgtRC2YLZwtvC3sLjwueC60LvAvAC88L3gvtC/wMCwwXDCkMOAw5DEgMVwxmDGcMdgyFDJQMlwybDJ8MowyrDK4MsgyzVSRudWxs1wAJAAoACwAMAA0ADgAPABAAEQASABMAFAATABZfEA9feGRfcm9vdFBhY2thZ2VWJGNsYXNzXF94ZF9jb21tZW50c18QEF94ZF9tb2RlbE1hbmFnZXJfEBVfY29uZmlndXJhdGlvbnNCeU5hbWVdX3hkX21vZGVsTmFtZV8QF19tb2RlbFZlcnNpb25JZGVudGlmaWVygAKBAS+BASyAAIEBLYAAgQEu3gAYABkAGgAbABwAHQAeAAoAHwAgACEAIgAjACQAJQAmACcAKAAlABMAKwAsAC0ALgAvACUAJQATXxAcWERCdWNrZXRGb3JDbGFzc2Vzd2FzRW5jb2RlZF8QGlhEQnVja2V0Rm9yUGFja2FnZXNzdG9yYWdlXxAcWERCdWNrZXRGb3JJbnRlcmZhY2Vzc3RvcmFnZV8QD194ZF9vd25pbmdNb2RlbF8QHVhEQnVja2V0Rm9yUGFja2FnZXN3YXNFbmNvZGVkVl9vd25lcl8QG1hEQnVja2V0Rm9yRGF0YVR5cGVzc3RvcmFnZVtfdmlzaWJpbGl0eV8QGVhEQnVja2V0Rm9yQ2xhc3Nlc3N0b3JhZ2VVX25hbWVfEB9YREJ1Y2tldEZvckludGVyZmFjZXN3YXNFbmNvZGVkXxAeWERCdWNrZXRGb3JEYXRhVHlwZXN3YXNFbmNvZGVkXxAQX3VuaXF1ZUVsZW1lbnRJRIAEgQEqgQEogAGABIAAgQEpgQErEACABYADgASABIAAUFNZRVPTADYANwAKADgAOwA+V05TLmtleXNaTlMub2JqZWN0c6IAOQA6gAaAB6IAPAA9gAiAe4AmV1NlY3Rpb25UUG9zdN8QEABCAEMARABFAB0ARgBHAB8ASABJAAoAIQBKAEsAJABMAE0ATgAlACUAEABSAFMALQAlAE0AVgA5AE0AWQBaAFtfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2VfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNkdXBsaWNhdGVzXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3N0b3JhZ2VbX2lzQWJzdHJhY3SACoAtgASABIACgAuA8YAEgAqA84AGgAqBASeACQgSJmOFQVdvcmRlcmVk0wA2ADcACgBfAGEAPqEAYIAMoQBigA2AJl5YRF9QU3RlcmVvdHlwZdkAHQAhAGYACgAkAGcAHwBMAGgAPABgAE0AbAATACUALQBbAHBfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WACIAMgAqALIAAgAQIgA7TADYANwAKAHIAfAA+qQBzAHQAdQB2AHcAeAB5AHoAe4APgBCAEYASgBOAFIAVgBaAF6kAfQB+AH8AgACBAIIAgwCEAIWAGIAcgB2AH4AggCKAJIAngCuAJl8QE1hEUE1Db21wb3VuZEluZGV4ZXNfEBBYRF9QU0tfZWxlbWVudElEXxAZWERQTVVuaXF1ZW5lc3NDb25zdHJhaW50c18QGlhEX1BTS192ZXJzaW9uSGFzaE1vZGlmaWVyXxAZWERfUFNLX2ZldGNoUmVxdWVzdHNBcnJheV8QEVhEX1BTS19pc0Fic3RyYWN0XxAPWERfUFNLX3VzZXJJbmZvXxATWERfUFNLX2NsYXNzTWFwcGluZ18QFlhEX1BTS19lbnRpdHlDbGFzc05hbWXfEA8AkQCSAJMAHQCUAJUAlgAfAJcACgAhAJgAmQAkAJoAEwCcABMAYgBbAFsAWwAtAFsAowBzAFsAWwATAFtVX3R5cGVYX2RlZmF1bHRcX2Fzc29jaWF0aW9uW19pc1JlYWRPbmx5WV9pc1N0YXRpY1lfaXNVbmlxdWVaX2lzRGVyaXZlZFpfaXNPcmRlcmVkXF9pc0NvbXBvc2l0ZVdfaXNMZWFmgACAGYAAgA0ICAgIgBuADwgIgAAI0gA3AAoAqgCroIAa0gCtAK4ArwCwWiRjbGFzc25hbWVYJGNsYXNzZXNeTlNNdXRhYmxlQXJyYXmjAK8AsQCyV05TQXJyYXlYTlNPYmplY3TSAK0ArgC0ALVfEBBYRFVNTFByb3BlcnR5SW1wpAC2ALcAuACyXxAQWERVTUxQcm9wZXJ0eUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w3xAPAJEAkgCTAB0AlACVAJYAHwCXAAoAIQCYAJkAJACaABMAEwATAGIAWwBbAFsALQBbAKMAdABbAFsAEwBbgACAAIAAgA0ICAgIgBuAEAgIgAAI3xAPAJEAkgCTAB0AlACVAJYAHwCXAAoAIQCYAJkAJACaABMAygATAGIAWwBbAFsALQBbAKMAdQBbAFsAEwBbgACAHoAAgA0ICAgIgBuAEQgIgAAI0gA3AAoA2ACroIAa3xAPAJEAkgCTAB0AlACVAJYAHwCXAAoAIQCYAJkAJACaABMAEwATAGIAWwBbAFsALQBbAKMAdgBbAFsAEwBbgACAAIAAgA0ICAgIgBuAEggIgAAI3xAPAJEAkgCTAB0AlACVAJYAHwCXAAoAIQCYAJkAJACaABMA6wATAGIAWwBbAFsALQBbAKMAdwBbAFsAEwBbgACAIYAAgA0ICAgIgBuAEwgIgAAI0gA3AAoA+QCroIAa3xAPAJEAkgCTAB0AlACVAJYAHwCXAAoAIQCYAJkAJACaABMA/QATAGIAWwBbAFsALQBbAKMAeABbAFsAEwBbgACAI4AAgA0ICAgIgBuAFAgIgAAICN8QDwCRAJIAkwAdAJQAlQCWAB8AlwAKACEAmACZACQAmgATAQ0AEwBiAFsAWwBbAC0AWwCjAHkAWwBbABMAW4AAgCWAAIANCAgICIAbgBUICIAACNMANgA3AAoBGwEcAD6goIAm0gCtAK4BHwEgXxATTlNNdXRhYmxlRGljdGlvbmFyeaMBHwEhALJcTlNEaWN0aW9uYXJ53xAPAJEAkgCTAB0AlACVAJYAHwCXAAoAIQCYAJkAJACaABMBJAATAGIAWwBbAFsALQBbAKMAegBbAFsAEwBbgACAKIAAgA0ICAgIgBuAFggIgAAI1gAhAAoAJABMAB0AHwEyATMAEwBbABMALYApgCqAAAiAAF8QFFhER2VuZXJpY1JlY29yZENsYXNz0gCtAK4BOQE6XVhEVU1MQ2xhc3NJbXCmATsBPAE9AT4BPwCyXVhEVU1MQ2xhc3NJbXBfEBJYRFVNTENsYXNzaWZpZXJJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w3xAPAJEAkgCTAB0AlACVAJYAHwCXAAoAIQCYAJkAJACaABMAOQATAGIAWwBbAFsALQBbAKMAewBbAFsAEwBbgACABoAAgA0ICAgIgBuAFwgIgAAI0gCtAK4BUAFRXxASWERVTUxTdGVyZW90eXBlSW1wpwFSAVMBVAFVAVYBVwCyXxASWERVTUxTdGVyZW90eXBlSW1wXVhEVU1MQ2xhc3NJbXBfEBJYRFVNTENsYXNzaWZpZXJJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0wA2ADcACgFZAV4APqQBWgFbAVwBXYAugC+AMIAxpAFfAWABYQFigDKAXYD3gQEOgCZUYm9keVRwb3N0VXRpdGxlVWluZGV43xASAJEAkgCTAWkAHQCVAJYBagAfAJQBawCXAAoAIQCYAJkAJACaABMAEwATACUAPABbAFsBcwAtAFsATQBbAXcBWgBbAFsBewBbXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASACAgIgDQIgAoIgFyALggIgDMIElnkWAbTADYANwAKAX8BggA+ogGAAYGANYA2ogGDAYSAN4BLgCZfEBJYRF9QUHJvcFN0ZXJlb3R5cGVfEBJYRF9QQXR0X1N0ZXJlb3R5cGXZAB0AIQGJAAoAJAGKAB8ATAGLAV8BgABNAGwAEwAlAC0AWwGTXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgDKANYAKgCyAAIAECIA40wA2ADcACgGVAZ4APqgBlgGXAZgBmQGaAZsBnAGdgDmAOoA7gDyAPYA+gD+AQKgBnwGgAaEBogGjAaQBpQGmgEGAQoBDgEWARoBIgEmASoAmXxAbWERfUFBTS19pc1N0b3JlZEluVHJ1dGhGaWxlXxAbWERfUFBTS192ZXJzaW9uSGFzaE1vZGlmaWVyXxAQWERfUFBTS191c2VySW5mb18QEVhEX1BQU0tfaXNJbmRleGVkXxASWERfUFBTS19pc09wdGlvbmFsXxAaWERfUFBTS19pc1Nwb3RsaWdodEluZGV4ZWRfEBFYRF9QUFNLX2VsZW1lbnRJRF8QE1hEX1BQ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CIAACNIANwAKDJUAq6CAGtMANgA3AAoMmAyZAD6goIAm0wA2ADcACgycDJ0APqCggCbTADYANwAKDKAMoQA+oKCAJtIArQCuDKQMpV5YRE1vZGVsUGFja2FnZaYMpgynDKgMqQyqALJeWERNb2RlbFBhY2thZ2VfEA9YRFVNTFBhY2thZ2VJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0gA3AAoMrACroIAa0wA2ADcACgyvDLAAPqCggCZQ0gCtAK4MtAy1WVhEUE1Nb2RlbKMMtAy2ALJXWERNb2RlbF8QD05TS2V5ZWRBcmNoaXZlctEMuQAoVHJvb3SAAQAIABkAIgArADUAOgA/AqMCqQLGAtgC3wLsAv8DFwMlAz8DQQNEA0cDSQNMA04DUQOKA6kDxgPlA/cEFwQeBDwESARkBGoEjAStBMAEwgTFBMgEygTMBM4E0QTUBNYE2ATaBNwE3gTgBOEE5QTyBPoFBQUKBQwFDgUTBRUFFwUZBSEFJgVpBY0FsQXUBfsGGwZCBmkGiQatBtEG3QbfBuEG4wblBucG6QbrBu0G7wbxBvMG9Qb4BvoG+wcABwgHFQcYBxoHHQcfByEHMAdVB3kHoAfEB8YHyAfKB8wHzgfQB9EH0wfgB/MH9Qf3B/kH+wf9B/8IAQgDCAUIGAgaCBwIHgggCCIIJAgmCCgIKggsCEIIVQhxCI4Iqgi+CNAI5gj/CT4JRAlNCVoJZglwCXoJhQmQCZ0JpQmnCakJqwmtCa4JrwmwCbEJswm1CbYJtwm5CboJwwnECcYJzwnaCeMJ8gn5CgEKCgoTCiYKLwpCClkKawqqCqwKrgqwCrIKswq0CrUKtgq4CroKuwq8Cr4Kvwr+CwALAgsECwYLBwsICwkLCgsMCw4LDwsQCxILEwscCx0LHwteC2ALYgtkC2YLZwtoC2kLagtsC24LbwtwC3ILcwuyC7QLtgu4C7oLuwu8C70LvgvAC8ILwwvEC8YLxwvQC9EL0wwSDBQMFgwYDBoMGwwcDB0MHgwgDCIMIwwkDCYMJwwoDGcMaQxrDG0MbwxwDHEMcgxzDHUMdwx4DHkMewx8DIkMigyLDI0MlgysDLMMwAz/DQENAw0FDQcNCA0JDQoNCw0NDQ8NEA0RDRMNFA0tDS8NMQ0zDTQNNg1NDVYNZA1xDX8NlA2oDb8N0Q4QDhIOFA4WDhgOGQ4aDhsOHA4eDiAOIQ4iDiQOJQ4uDkMOUg5nDnUOig6eDrUOxw7UDt0O3w7hDuMO5Q7uDvAO8g70DvcO+Q7+DwMPCQ8PD1oPfQ+dD70Pvw/BD8MPxQ/HD8gPyQ/LD8wPzg/PD9EP0w/UD9UP1w/YD90P6g/vD/EP8w/4D/oP/A/+EBMQKBBNEHEQmBC8EL4QwBDCEMQQxhDIEMkQyxDYEOkQ6xDtEO8Q8RDzEPUQ9xD5EQoRDBEOERAREhEUERYRGBEaERwROhFYEWsRfxGUEbERxRHbEhoSHBIeEiASIhIjEiQSJRImEigSKhIrEiwSLhIvEm4ScBJyEnQSdhJ3EngSeRJ6EnwSfhJ/EoASghKDEsISxBLGEsgSyhLLEswSzRLOEtAS0hLTEtQS1hLXEuQS5RLmEugTJxMpEysTLRMvEzATMRMyEzMTNRM3EzgTORM7EzwTexN9E38TgRODE4QThROGE4cTiROLE4wTjROPE5ATkRPQE9IT1BPWE9gT2RPaE9sT3BPeE+AT4RPiE+QT5RQkFCYUKBQqFCwULRQuFC8UMBQyFDQUNRQ2FDgUORR4FHoUfBR+FIAUgRSCFIMUhBSGFIgUiRSKFIwUjRSyFNYU/RUhFSMVJRUnFSkVKxUtFS4VMBU9FUwVThVQFVIVVBVWFVgVWhVpFWsVbRVvFXEVcxV1FXcVeRWZFcQV3hX3FhEWMRZUFpMWlRaXFpkWmxacFp0WnhafFqEWoxakFqUWpxaoFucW6RbrFu0W7xbwFvEW8hbzFvUW9xb4FvkW+xb8FzsXPRc/F0EXQxdEF0UXRhdHF0kXSxdMF00XTxdQF48XkReTF5UXlxeYF5kXmhebF50XnxegF6EXoxekF6cX5hfoF+oX7BfuF+8X8BfxF/IX9Bf2F/cX+Bf6F/sYOhg8GD4YQBhCGEMYRBhFGEYYSBhKGEsYTBhOGE8YjhiQGJIYlBiWGJcYmBiZGJoYnBieGJ8YoBiiGKMYrBi6GMcY1RjiGPUZDBkeGWkZjBmsGcwZzhnQGdIZ1BnWGdcZ2BnaGdsZ3RneGeAZ4hnjGeQZ5hnnGewZ+Rn+GgAaAhoHGgkaCxoNGiAaRRppGpAatBq2Grgauhq8Gr4awBrBGsMa0BrhGuMa5RrnGuka6xrtGu8a8RsCGwQbBhsIGwobDBsOGxAbEhsUG1MbVRtXG1kbWxtcG10bXhtfG2EbYxtkG2UbZxtoG6cbqRurG60brxuwG7EbshuzG7Ubtxu4G7kbuxu8G/sb/Rv/HAEcAxwEHAUcBhwHHAkcCxwMHA0cDxwQHB0cHhwfHCEcYBxiHGQcZhxoHGkcahxrHGwcbhxwHHEcchx0HHUctBy2HLgcuhy8HL0cvhy/HMAcwhzEHMUcxhzIHMkdCB0KHQwdDh0QHREdEh0THRQdFh0YHRkdGh0cHR0dXB1eHWAdYh1kHWUdZh1nHWgdah1sHW0dbh1wHXEdsB2yHbQdth24Hbkduh27Hbwdvh3AHcEdwh3EHcUd6h4OHjUeWR5bHl0eXx5hHmMeZR5mHmgedR6EHoYeiB6KHowejh6QHpIeoR6jHqUepx6pHqserR6vHrEewx7XHuke/h8QHx8fPB97H30ffx+BH4MfhB+FH4Yfhx+JH4sfjB+NH48fkB/PH9Ef0x/VH9cf2B/ZH9of2x/dH98f4B/hH+Mf5B/mICUgJyApICsgLSAuIC8gMCAxIDMgNSA2IDcgOSA6IHkgeyB9IH8ggSCCIIMghCCFIIcgiSCKIIsgjSCOINEg9SEZITwhYyGDIaoh0SHxIhUiOSI7Ij0iPyJBIkMiRSJHIkkiSyJNIk8iUSJTIlUiViJbImgiayJtInAiciJ0IpkivSLkIwgjCiMMIw4jECMSIxQjFSMXIyQjNyM5IzsjPSM/I0EjQyNFI0cjSSNcI14jYCNiI2QjZiNoI2ojbCNuI3AjryOxI7MjtSO3I7gjuSO6I7sjvSO/I8AjwSPDI8QjzSPOI9AkDyQRJBMkFSQXJBgkGSQaJBskHSQfJCAkISQjJCQkYyRlJGckaSRrJGwkbSRuJG8kcSRzJHQkdSR3JHgkgSSCJIQkwyTFJMckySTLJMwkzSTOJM8k0STTJNQk1STXJNglFyUZJRslHSUfJSAlISUiJSMlJSUnJSglKSUrJSwlNSU2JTgldyV5JXslfSV/JYAlgSWCJYMlhSWHJYgliSWLJYwlyyXNJc8l0SXTJdQl1SXWJdcl2SXbJdwl3SXfJeAl7SXuJe8l8SYwJjImNCY2JjgmOSY6JjsmPCY+JkAmQSZCJkQmRSaEJoYmiCaKJowmjSaOJo8mkCaS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UxlTJlMnUyhTKlNpU2tTbVNvU3JTc1N0U3VTdlN4U3pTe1N8U35Tf1O+U8BTwlPEU8dTyFPJU8pTy1PNU89T0FPRU9NT1FQTVBVUF1QZVBxUHVQeVB9UIFQiVCRUJVQmVChUKVRoVGpUbFRuVHFUclRzVHRUdVR3VHlUelR7VH1UflS9VL9UwVTDVMZUx1TIVMlUylTMVM5Uz1TQVNJU01T4VRxVQ1VnVWpVbFVuVXBVclV0VXVVeFWFVZRVllWYVZpVnFWeVaBVolWxVbRVt1W6Vb1VwFXDVcZVyFYHVglWDFYOVhFWElYTVhRWFVYXVhlWGlYbVh1WHlYgVl9WYVZjVmVWaFZpVmpWa1ZsVm5WcFZxVnJWdFZ1VrRWtla4VrpWvVa+Vr9WwFbBVsNWxVbGVsdWyVbKVwlXC1cOVxBXE1cUVxVXFlcXVxlXG1ccVx1XH1cgVyJXYVdjV2VXZ1dqV2tXbFdtV25XcFdyV3NXdFd2V3dXtle4V7pXvFe/V8BXwVfCV8NXxVfHV8hXyVfLV8xYC1gNWA9YEVgUWBVYFlgXWBhYGlgcWB1YHlggWCFYKlgrWC1YOlg7WDxYPlhLWExYTVhPWFxYXVheWGBYaVh4WIVYlFimWLpY0VjjWOxY7VjvWPxY/Vj+WQBZAVkKWRRZG1kjWTVZOlk/AAAAAAAAAgIAAAAAAAAMuwAAAAAAAAAAAAAAAAAAWUE= 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/CoreData/Migration/Policies/Post2ToPost3MigrationPolicy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostToColorV3MigrationPolicy.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 12/09/2017. 6 | // Copyright © 2017 William Boles. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | final class Post2ToPost3MigrationPolicy: NSEntityMigrationPolicy { 12 | 13 | override func createDestinationInstances(forSource sourceInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws { 14 | try super.createDestinationInstances(forSource: sourceInstance, in: mapping, manager: manager) 15 | 16 | guard let destinationPost = manager.destinationInstances(forEntityMappingName: mapping.name, sourceInstances: [sourceInstance]).first else { 17 | fatalError("was expected a post") 18 | } 19 | 20 | let sourceBody = sourceInstance.value(forKey: "content") as? String 21 | let sourceTitle = sourceBody?.prefix(4).appending("...") 22 | 23 | let section = NSEntityDescription.insertNewObject(forEntityName: "Section", into: destinationPost.managedObjectContext!) 24 | section.setValue(sourceTitle, forKey: "title") 25 | section.setValue(sourceBody, forKey: "body") 26 | section.setValue(destinationPost, forKey: "post") 27 | section.setValue(0, forKey: "index") 28 | 29 | var sections = Set() 30 | sections.insert(section) 31 | 32 | destinationPost.setValue(sections, forKey: "sections") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/CoreData/Model/CoreDataMigration_Example.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | CoreDataMigration_Example 4.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/CoreData/Model/CoreDataMigration_Example.xcdatamodeld/CoreDataMigration_Example 2.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/CoreData/Model/CoreDataMigration_Example.xcdatamodeld/CoreDataMigration_Example 3.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/CoreData/Model/CoreDataMigration_Example.xcdatamodeld/CoreDataMigration_Example 4.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/CoreData/Model/CoreDataMigration_Example.xcdatamodeld/CoreDataMigration_Example.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/Extensions/FileManager/FileManager+ApplicationSupport.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileManager+ApplicationSupport.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 17/01/2019. 6 | // Copyright © 2019 William Boles. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import Foundation 12 | 13 | extension FileManager { 14 | 15 | // MARK: - ApplicationSupport 16 | 17 | static func clearApplicationSupportDirectoryContents() { 18 | guard let applicationSupportURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first, let applicationSupportDirectoryContents = try? FileManager.default.contentsOfDirectory(atPath: applicationSupportURL.path) else { 19 | return 20 | } 21 | applicationSupportDirectoryContents.forEach { 22 | let fileURL = URL(fileURLWithPath: applicationSupportURL.path, isDirectory: true).appendingPathComponent($0) 23 | try? FileManager.default.removeItem(atPath: fileURL.path) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/Extensions/NSManagedObjectModel/NSManagedObjectModel+Compatible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObjectModel+Compatible.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 02/01/2019. 6 | // Copyright © 2019 William Boles. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | extension NSManagedObjectModel { 13 | 14 | // MARK: - Compatible 15 | 16 | static func compatibleModelForStoreMetadata(_ metadata: [String : Any]) -> NSManagedObjectModel? { 17 | let mainBundle = Bundle.main 18 | return NSManagedObjectModel.mergedModel(from: [mainBundle], forStoreMetadata: metadata) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/Extensions/NSManagedObjectModel/NSManagedObjectModel+Resource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObjectModel+Resource.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 02/01/2019. 6 | // Copyright © 2019 William Boles. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | extension NSManagedObjectModel { 13 | 14 | // MARK: - Resource 15 | 16 | static func managedObjectModel(forResource resource: String) -> NSManagedObjectModel { 17 | let mainBundle = Bundle.main 18 | let subdirectory = "CoreDataMigration_Example.momd" 19 | 20 | var omoURL: URL? 21 | if #available(iOS 11, *) { 22 | omoURL = mainBundle.url(forResource: resource, withExtension: "omo", subdirectory: subdirectory) // optimized model file 23 | } 24 | let momURL = mainBundle.url(forResource: resource, withExtension: "mom", subdirectory: subdirectory) 25 | 26 | guard let url = omoURL ?? momURL else { 27 | fatalError("unable to find model in bundle") 28 | } 29 | 30 | guard let model = NSManagedObjectModel(contentsOf: url) else { 31 | fatalError("unable to load model in bundle") 32 | } 33 | 34 | return model 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/Extensions/NSPersistentStoreCoordinator/NSPersistentStoreCoordinator+SQLite.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSPersistentStoreCoordinator+SQLite.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 15/09/2017. 6 | // Copyright © 2017 William Boles. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | extension NSPersistentStoreCoordinator { 12 | 13 | // MARK: - Destroy 14 | 15 | static func destroyStore(at storeURL: URL) { 16 | do { 17 | let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: NSManagedObjectModel()) 18 | try persistentStoreCoordinator.destroyPersistentStore(at: storeURL, ofType: NSSQLiteStoreType, options: nil) 19 | } catch let error { 20 | fatalError("failed to destroy persistent store at \(storeURL), error: \(error)") 21 | } 22 | } 23 | 24 | // MARK: - Replace 25 | 26 | static func replaceStore(at targetURL: URL, withStoreAt sourceURL: URL) { 27 | do { 28 | let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: NSManagedObjectModel()) 29 | try persistentStoreCoordinator.replacePersistentStore(at: targetURL, destinationOptions: nil, withPersistentStoreFrom: sourceURL, sourceOptions: nil, ofType: NSSQLiteStoreType) 30 | } catch let error { 31 | fatalError("failed to replace persistent store at \(targetURL) with \(sourceURL), error: \(error)") 32 | } 33 | } 34 | 35 | // MARK: - Meta 36 | 37 | static func metadata(at storeURL: URL) -> [String : Any]? { 38 | return try? NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: storeURL, options: nil) 39 | } 40 | 41 | // MARK: - Add 42 | 43 | func addPersistentStore(at storeURL: URL, options: [AnyHashable : Any]) -> NSPersistentStore { 44 | do { 45 | return try addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options) 46 | } catch let error { 47 | fatalError("failed to add persistent store to coordinator, error: \(error)") 48 | } 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/Extensions/UIColor/UIColor+Hex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Hex.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 12/09/2017. 6 | // Copyright © 2017 William Boles. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | // MARK: - Hex 14 | 15 | var hexString: String { 16 | let components = self.cgColor.components 17 | 18 | let red = Float((components?[0])!) 19 | let green = Float((components?[1])!) 20 | let blue = Float((components?[2])!) 21 | 22 | return String(format: "%02lX%02lX%02lX", lroundf(red * 255), lroundf(green * 255), lroundf(blue * 255)) 23 | } 24 | 25 | static func colorWithHex(hexColor: String) -> UIColor? { 26 | guard hexColor.count == 6 else { 27 | return nil 28 | } 29 | 30 | let scanner = Scanner(string: hexColor) 31 | var hexNumber: UInt64 = 0 32 | 33 | guard scanner.scanHexInt64(&hexNumber) else { 34 | return nil 35 | } 36 | 37 | let red = CGFloat((hexNumber & 0xff0000) >> 16) / 255 38 | let green = CGFloat((hexNumber & 0x00ff00) >> 8) / 255 39 | let blue = CGFloat(hexNumber & 0x0000ff) / 255 40 | let alpha = CGFloat(1) 41 | 42 | return UIColor(red: red, green: green, blue: blue, alpha: alpha) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/Extensions/UIColor/UIColor+Random.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Random.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 12/09/2017. 6 | // Copyright © 2017 William Boles. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | // MARK: - Random 14 | 15 | static var randomPastelColor: UIColor { 16 | let mixColor = UIColor.white 17 | 18 | let randomColorGenerator = { ()-> CGFloat in 19 | CGFloat(arc4random() % 256) / 256 20 | } 21 | 22 | var red: CGFloat = randomColorGenerator() 23 | var green: CGFloat = randomColorGenerator() 24 | var blue: CGFloat = randomColorGenerator() 25 | 26 | var mixRed: CGFloat = 0 27 | var mixGreen: CGFloat = 0 28 | var mixBlue: CGFloat = 0 29 | mixColor.getRed(&mixRed, green: &mixGreen, blue: &mixBlue, alpha: nil) 30 | 31 | red = (red + mixRed) / 2 32 | green = (green + mixGreen) / 2 33 | blue = (blue + mixBlue) / 2 34 | 35 | return UIColor(red: red, green: green, blue: blue, alpha: 1) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/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" : "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 | } -------------------------------------------------------------------------------- /CoreDataMigration-Example/Storyboards/AppLoading.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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/Storyboards/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 | 29 | 30 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/Storyboards/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 | 26 | 27 | 28 | 29 | 30 | 31 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 125 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 228 | 229 | 230 | 231 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/ViewControllers/Loading/AppLoadingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppLoadingViewController.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 12/09/2017. 6 | // Copyright © 2017 William Boles. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AppLoadingViewController: UIViewController { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/ViewControllers/Posts/PostTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostTableViewCell.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 11/09/2017. 6 | // Copyright © 2017 William Boles. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct PostTableViewCellViewModel { 12 | 13 | let preview: String 14 | let date: String 15 | let backgroundColor: UIColor 16 | } 17 | 18 | class PostTableViewCell: UITableViewCell { 19 | 20 | @IBOutlet weak var contentLabel: UILabel! 21 | @IBOutlet weak var dateLabel: UILabel! 22 | 23 | // MARK: - Configure 24 | 25 | func configure(withViewModel viewModel: PostTableViewCellViewModel) { 26 | contentLabel.text = viewModel.preview 27 | dateLabel.text = viewModel.date 28 | contentView.backgroundColor = viewModel.backgroundColor 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/ViewControllers/Posts/PostsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostsViewController.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 11/09/2017. 6 | // Copyright © 2017 William Boles. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class PostsViewController: UITableViewController { 13 | 14 | var posts = [Post]() 15 | 16 | lazy var dateFormatter: DateFormatter = { 17 | let dateFormatter = DateFormatter() 18 | dateFormatter.dateStyle = .medium 19 | dateFormatter.timeStyle = .medium 20 | 21 | return dateFormatter 22 | }() 23 | 24 | // MARK: - ViewLifecycle 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | 29 | tableView.rowHeight = 80.0 30 | tableView.separatorColor = UIColor.clear 31 | } 32 | 33 | override func viewWillAppear(_ animated: Bool) { 34 | super.viewWillAppear(animated) 35 | 36 | loadData() 37 | } 38 | 39 | // MARK: - Segue 40 | 41 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 42 | if segue.identifier == "Viewer" { 43 | if let postViewCcontroller = segue.destination as? PostViewerViewController, let tableViewCell = sender as? UITableViewCell, let indexPath = tableView.indexPath(for: tableViewCell) { 44 | let post = posts[indexPath.row] 45 | let viewModel = postViewerViewModel(forPost: post) 46 | postViewCcontroller.configure(withViewModel: viewModel) 47 | } 48 | } 49 | } 50 | 51 | // MARK: - Load 52 | 53 | private func loadData() { 54 | let context = CoreDataManager.shared.mainContext 55 | let request = NSFetchRequest.init(entityName: "Post") 56 | let dateSort = NSSortDescriptor(key: "date", ascending: false) 57 | let predicate = NSPredicate(format: "softDeleted == NO") 58 | 59 | request.sortDescriptors = [dateSort] 60 | request.predicate = predicate 61 | posts = try! context.fetch(request) 62 | 63 | tableView.reloadData() 64 | } 65 | 66 | // MARK: - UITableViewDataSource 67 | 68 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 69 | return posts.count 70 | } 71 | 72 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 73 | let post = posts[indexPath.row] 74 | 75 | let cell = tableView.dequeueReusableCell(withIdentifier: "PostTableViewCell", for: indexPath) as! PostTableViewCell 76 | 77 | let viewModel = cellViewModel(forPost: post) 78 | cell.configure(withViewModel: viewModel) 79 | 80 | return cell 81 | } 82 | 83 | // MARK: - ViewModels 84 | 85 | private func cellViewModel(forPost post: Post) -> PostTableViewCellViewModel { 86 | let backgroundColor = UIColor.colorWithHex(hexColor: post.hexColor!) ?? UIColor.white 87 | let formattedDate = dateFormatter.string(from: post.date!) 88 | let typedSections = post.sections as! Set
89 | let firstSection = typedSections.sorted { $0.index < $1.index }.first! 90 | let preview = firstSection.title!.count > 0 ? firstSection.title! : firstSection.body! 91 | 92 | return PostTableViewCellViewModel(preview: preview, date: formattedDate, backgroundColor: backgroundColor) 93 | } 94 | 95 | private func postViewerViewModel(forPost post: Post) -> PostViewerViewModel { 96 | let backgroundColor = UIColor.colorWithHex(hexColor: post.hexColor!) ?? UIColor.white 97 | let typedSections = post.sections as! Set
98 | 99 | let sections = typedSections.sorted { $0.index < $1.index }.map { PostViewerSectionViewModel(title: $0.title!, body: $0.body!) } 100 | 101 | return PostViewerViewModel(postID: post.postID!, sections: sections, backgroundColor: backgroundColor) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/ViewControllers/Viewer/PostSectionViewerTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostSectionViewerTableViewCell.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by Boles, William (Developer) on 17/01/2019. 6 | // Copyright © 2019 William Boles. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PostSectionViewerTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var titleLabel: UILabel! 14 | @IBOutlet weak var bodyLabel: UILabel! 15 | 16 | // MARK: - Reuse 17 | 18 | override func prepareForReuse() { 19 | super.prepareForReuse() 20 | 21 | titleLabel.text = nil 22 | bodyLabel.text = nil 23 | } 24 | 25 | // MARK: - Configure 26 | 27 | func configure(withViewModel viewModel: PostViewerSectionViewModel) { 28 | titleLabel.text = viewModel.title 29 | bodyLabel.text = viewModel.body 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/ViewControllers/Viewer/PostViewerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostViewerViewController.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 13/01/2019. 6 | // Copyright © 2019 William Boles. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | struct PostViewerViewModel { 13 | 14 | let postID: String 15 | let sections: [PostViewerSectionViewModel] 16 | let backgroundColor: UIColor 17 | } 18 | 19 | struct PostViewerSectionViewModel { 20 | 21 | let title: String 22 | let body: String 23 | } 24 | 25 | class PostViewerViewController: UITableViewController { 26 | 27 | private var viewModel: PostViewerViewModel! 28 | 29 | // MARK: - ViewLifecycle 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | 34 | tableView.tableFooterView = UIView() 35 | } 36 | 37 | override func viewWillAppear(_ animated: Bool) { 38 | super.viewWillAppear(animated) 39 | 40 | tableView.backgroundColor = viewModel.backgroundColor 41 | } 42 | 43 | // MARK: - Configure 44 | 45 | func configure(withViewModel viewModel: PostViewerViewModel) { 46 | self.viewModel = viewModel 47 | 48 | tableView.reloadData() 49 | } 50 | 51 | // MARK: - UITableViewDataSource 52 | 53 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 54 | return viewModel.sections.count 55 | } 56 | 57 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 58 | let contentSectionViewModel = viewModel.sections[indexPath.row] 59 | 60 | let cell = tableView.dequeueReusableCell(withIdentifier: "PostSectionViewerTableViewCell", for: indexPath) as! PostSectionViewerTableViewCell 61 | 62 | cell.configure(withViewModel: contentSectionViewModel) 63 | 64 | return cell 65 | } 66 | 67 | // MARK: - ButtonActions 68 | 69 | @IBAction func deleteButtonPressed(_ sender: Any) { 70 | DispatchQueue.global(qos: .userInitiated).async { 71 | let context = CoreDataManager.shared.backgroundContext 72 | context.performAndWait { 73 | let request = NSFetchRequest.init(entityName: "Post") 74 | request.predicate = NSPredicate(format: "postID == '\(self.viewModel.postID)'") 75 | 76 | let post = try! context.fetch(request).first! 77 | post.softDeleted = true 78 | 79 | try? context.save() 80 | 81 | DispatchQueue.main.async { 82 | self.navigationController?.popViewController(animated: true) 83 | } 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/ViewControllers/Writer/PostSectionWriterTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostSectionTableViewCell.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 15/01/2019. 6 | // Copyright © 2019 William Boles. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct PostSectionWriterTableViewCellViewModel { 12 | 13 | var title: String 14 | var body: String 15 | } 16 | 17 | protocol PostSectionWriterTableViewCellDelegate: class { 18 | 19 | func didSetTitle(cell: PostSectionWriterTableViewCell, to title: String) 20 | func didSetBody(cell: PostSectionWriterTableViewCell, to body: String) 21 | } 22 | 23 | class PostSectionWriterTableViewCell: UITableViewCell, UITextFieldDelegate, UITextViewDelegate { 24 | 25 | @IBOutlet weak var titleTextField: UITextField! 26 | @IBOutlet weak var bodyTextView: UITextView! 27 | 28 | weak var delegate: PostSectionWriterTableViewCellDelegate? 29 | 30 | // MARK: - Awake 31 | 32 | override func awakeFromNib() { 33 | super.awakeFromNib() 34 | 35 | titleTextField.delegate = self 36 | bodyTextView.delegate = self 37 | } 38 | 39 | // MARK: - Configure 40 | 41 | func configure(withViewModel viewModel: PostSectionWriterTableViewCellViewModel) { 42 | titleTextField.text = viewModel.title 43 | bodyTextView.text = viewModel.body 44 | } 45 | 46 | // MARK: - UITextFieldDelegate 47 | 48 | func textFieldDidEndEditing(_ textField: UITextField) { 49 | delegate?.didSetTitle(cell: self, to: textField.text ?? "") 50 | } 51 | 52 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 53 | textField.resignFirstResponder() 54 | 55 | return true 56 | } 57 | 58 | // MARK: - UITextViewDelegate 59 | 60 | func textViewDidEndEditing(_ textView: UITextView) { 61 | delegate?.didSetBody(cell: self, to: textView.text ?? "") 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /CoreDataMigration-Example/ViewControllers/Writer/PostWriterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostWriterViewController.swift 3 | // CoreDataMigration-Example 4 | // 5 | // Created by William Boles on 13/01/2019. 6 | // Copyright © 2019 William Boles. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class PostWriterViewController: UITableViewController, PostSectionWriterTableViewCellDelegate { 13 | 14 | var contentSectionViewModels = [PostSectionWriterTableViewCellViewModel]() 15 | 16 | // MARK: - ViewLifecycle 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | tableView.rowHeight = 220.0 22 | tableView.tableFooterView = UIView() 23 | } 24 | 25 | override func viewWillAppear(_ animated: Bool) { 26 | super.viewWillAppear(animated) 27 | 28 | addNewSectionToTableView() 29 | } 30 | 31 | // MARK: - UITableViewDataSource 32 | 33 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 34 | return contentSectionViewModels.count 35 | } 36 | 37 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 38 | let contentSectionViewModel = contentSectionViewModels[indexPath.row] 39 | 40 | let cell = tableView.dequeueReusableCell(withIdentifier: "PostSectionTableViewCell", for: indexPath) as! PostSectionWriterTableViewCell 41 | 42 | cell.configure(withViewModel: contentSectionViewModel) 43 | cell.delegate = self 44 | 45 | return cell 46 | } 47 | 48 | // MARK: - ButtonActions 49 | 50 | @IBAction func saveButtonPressed(_ sender: Any) { 51 | view.endEditing(true) 52 | 53 | DispatchQueue.global(qos: .userInitiated).async { 54 | let context = CoreDataManager.shared.backgroundContext 55 | context.performAndWait { 56 | let post = NSEntityDescription.insertNewObject(forEntityName: "Post", into: context) as! Post 57 | post.postID = UUID().uuidString 58 | post.date = Date() 59 | post.hexColor = UIColor.randomPastelColor.hexString 60 | 61 | for (index, viewModel) in self.contentSectionViewModels.enumerated() { 62 | guard viewModel.title.count > 0 || viewModel.body.count > 0 else { 63 | continue 64 | } 65 | 66 | let section = NSEntityDescription.insertNewObject(forEntityName: "Section", into: context) as! Section 67 | section.title = viewModel.title 68 | section.body = viewModel.body 69 | section.index = Int16(index) 70 | 71 | section.post = post 72 | post.addToSections(section) 73 | } 74 | 75 | if post.sections?.count ?? 0 > 0 { 76 | try? context.save() 77 | } 78 | 79 | DispatchQueue.main.async { 80 | self.dismiss(animated: true, completion: nil) 81 | } 82 | } 83 | } 84 | } 85 | 86 | @IBAction func addSectionButtonPressed(_ sender: Any) { 87 | view.endEditing(true) 88 | addNewSectionToTableView() 89 | scrollToLastSection() 90 | } 91 | 92 | @IBAction func cancelButtonPressed(_ sender: Any) { 93 | dismiss(animated: true, completion: nil) 94 | } 95 | 96 | // MARK: - Section 97 | 98 | func addNewSectionToTableView() { 99 | let viewModel = PostSectionWriterTableViewCellViewModel(title: "", body: "") 100 | contentSectionViewModels.append(viewModel) 101 | 102 | tableView.reloadData() 103 | } 104 | 105 | func scrollToLastSection() { 106 | let lastIndex = (contentSectionViewModels.count - 1) 107 | let lastIndexPath = IndexPath(item: lastIndex, section: 0) 108 | 109 | tableView.scrollToRow(at: lastIndexPath, at: .bottom, animated: true) 110 | } 111 | 112 | // MARK: - PostSectionWriterTableViewCellDelegate 113 | 114 | func didSetTitle(cell: PostSectionWriterTableViewCell, to title: String) { 115 | guard let indexPath = tableView.indexPath(for: cell) else { 116 | return 117 | } 118 | 119 | var viewModel = contentSectionViewModels[indexPath.row] 120 | viewModel.title = title 121 | 122 | contentSectionViewModels[indexPath.row] = viewModel 123 | } 124 | 125 | func didSetBody(cell: PostSectionWriterTableViewCell, to body: String) { 126 | guard let indexPath = tableView.indexPath(for: cell) else { 127 | return 128 | } 129 | 130 | var viewModel = contentSectionViewModels[indexPath.row] 131 | viewModel.body = body 132 | 133 | contentSectionViewModels[indexPath.row] = viewModel 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /CoreDataMigration-ExampleTests/Helpers/FileManager+Helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileManager+Helper.swift 3 | // CoreDataMigration-ExampleTests 4 | // 5 | // Created by William Boles on 05/01/2019. 6 | // Copyright © 2019 William Boles. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension FileManager { 12 | 13 | // MARK: - Temp 14 | 15 | static func clearTempDirectoryContents() { 16 | let tmpDirectoryContents = try! FileManager.default.contentsOfDirectory(atPath: NSTemporaryDirectory()) 17 | tmpDirectoryContents.forEach { 18 | let fileURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent($0) 19 | try? FileManager.default.removeItem(atPath: fileURL.path) 20 | } 21 | } 22 | 23 | static func moveFileFromBundleToTempDirectory(filename: String) -> URL { 24 | let destinationURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(filename) 25 | try? FileManager.default.removeItem(at: destinationURL) 26 | let bundleURL = Bundle(for: CoreDataMigratorTests.self).resourceURL!.appendingPathComponent(filename) 27 | try? FileManager.default.copyItem(at: bundleURL, to: destinationURL) 28 | 29 | return destinationURL 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /CoreDataMigration-ExampleTests/Helpers/NSManagedObjectContext+Helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObjectContext+Helper.swift 3 | // CoreDataMigration-ExampleTests 4 | // 5 | // Created by William Boles on 05/01/2019. 6 | // Copyright © 2019 William Boles. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | extension NSManagedObjectContext { 13 | 14 | // MARK: Model 15 | 16 | convenience init(model: NSManagedObjectModel, storeURL: URL) { 17 | let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model) 18 | try! persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: nil) 19 | 20 | self.init(concurrencyType: .mainQueueConcurrencyType) 21 | 22 | self.persistentStoreCoordinator = persistentStoreCoordinator 23 | } 24 | 25 | // MARK: - Destroy 26 | 27 | func destroyStore() { 28 | persistentStoreCoordinator?.persistentStores.forEach { 29 | try? persistentStoreCoordinator?.remove($0) 30 | try? persistentStoreCoordinator?.destroyPersistentStore(at: $0.url!, ofType: $0.type, options: nil) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /CoreDataMigration-ExampleTests/Supporting Files/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /CoreDataMigration-ExampleTests/Tests/CoreData/Manager/CoreDataManagerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataManagerTests.swift 3 | // CoreDataMigration-ExampleTests 4 | // 5 | // Created by William Boles on 15/09/2017. 6 | // Copyright © 2017 William Boles. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import CoreData 11 | 12 | @testable import CoreDataMigration_Example 13 | 14 | class CoreDataManagerTests: XCTestCase { 15 | 16 | var migrator: MockCoreDataMigrator! 17 | var sut: CoreDataManager! 18 | 19 | // MARK: - Lifecycle 20 | 21 | override func setUp() { 22 | super.setUp() 23 | 24 | migrator = MockCoreDataMigrator() 25 | sut = CoreDataManager(storeType: NSInMemoryStoreType, migrator: migrator) 26 | } 27 | 28 | override func tearDown() { 29 | migrator = nil 30 | sut = nil 31 | 32 | super.tearDown() 33 | } 34 | 35 | // MARK: - Tests 36 | 37 | // MARK: Setup 38 | 39 | func test_setup_loadsStore() { 40 | let promise = expectation(description: "calls back") 41 | sut.setup { 42 | XCTAssertTrue(self.sut.persistentContainer.persistentStoreCoordinator.persistentStores.count > 0) 43 | 44 | promise.fulfill() 45 | } 46 | 47 | waitForExpectations(timeout: 10) { error in 48 | if let error = error { 49 | print("Timed out: \(String(describing: error))") 50 | } 51 | } 52 | } 53 | 54 | func test_setup_checksIfMigrationRequired() { 55 | let promise = expectation(description: "calls back") 56 | sut.setup { 57 | XCTAssertTrue(self.migrator.requiresMigrationWasCalled) 58 | XCTAssertFalse(self.migrator.migrateStoreWasCalled) 59 | 60 | promise.fulfill() 61 | } 62 | 63 | waitForExpectations(timeout: 10) { error in 64 | if let error = error { 65 | print("Timed out: \(String(describing: error))") 66 | } 67 | } 68 | } 69 | 70 | func test_setup_migrate() { 71 | migrator.requiresMigrationToBeReturned = true 72 | 73 | let promise = expectation(description: "calls back") 74 | sut.setup { 75 | XCTAssertTrue(self.migrator.migrateStoreWasCalled) 76 | 77 | promise.fulfill() 78 | } 79 | 80 | waitForExpectations(timeout: 10) { error in 81 | if let error = error { 82 | print("Timed out: \(String(describing: error))") 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /CoreDataMigration-ExampleTests/Tests/CoreData/Migration/CoreDataMigratorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataMigratorTests.swift 3 | // CoreDataMigration-ExampleTests 4 | // 5 | // Created by William Boles on 12/09/2017. 6 | // Copyright © 2017 William Boles. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import CoreData 11 | 12 | @testable import CoreDataMigration_Example 13 | 14 | class CoreDataMigratorTests: XCTestCase { 15 | 16 | var sut: CoreDataMigrator! 17 | 18 | // MARK: - Lifecycle 19 | 20 | override class func setUp() { 21 | super.setUp() 22 | 23 | FileManager.clearTempDirectoryContents() 24 | } 25 | 26 | override func setUp() { 27 | super.setUp() 28 | 29 | sut = CoreDataMigrator() 30 | } 31 | 32 | override func tearDown() { 33 | sut = nil 34 | 35 | super.tearDown() 36 | } 37 | 38 | func tearDownCoreDataStack(context: NSManagedObjectContext) { 39 | context.destroyStore() 40 | } 41 | 42 | // MARK: - Tests 43 | 44 | // MARK: SingleStepMigrations 45 | 46 | func test_individualStepMigration_1to2() { 47 | let sourceURL = FileManager.moveFileFromBundleToTempDirectory(filename: "CoreDataMigration_Example_1.sqlite") 48 | let toVersion = CoreDataMigrationVersion.version2 49 | 50 | sut.migrateStore(at: sourceURL, toVersion: toVersion) 51 | 52 | XCTAssertTrue(FileManager.default.fileExists(atPath: sourceURL.path)) 53 | 54 | let model = NSManagedObjectModel.managedObjectModel(forResource: toVersion.rawValue) 55 | let context = NSManagedObjectContext(model: model, storeURL: sourceURL) 56 | let request = NSFetchRequest.init(entityName: "Post") 57 | let sort = NSSortDescriptor(key: "postID", ascending: false) 58 | request.sortDescriptors = [sort] 59 | 60 | let migratedPosts = try? context.fetch(request) 61 | 62 | XCTAssertEqual(migratedPosts?.count, 10) 63 | 64 | let firstMigratedPost = migratedPosts?.first 65 | 66 | let migratedDate = firstMigratedPost?.value(forKey: "date") as? Date 67 | let migratedHexColor = firstMigratedPost?.value(forKey: "hexColor") as? String 68 | let migratedPostID = firstMigratedPost?.value(forKey: "postID") as? String 69 | let migratedContent = firstMigratedPost?.value(forKey: "content") as? String 70 | 71 | XCTAssertEqual(migratedDate?.timeIntervalSince1970, 1547494150.058821) 72 | XCTAssertEqual(migratedHexColor, "1BB732") 73 | XCTAssertEqual(migratedPostID, "FFFECB21-6645-4FDD-B8B0-B960D0E61F5A") 74 | XCTAssertEqual(migratedContent, "Test body") 75 | 76 | tearDownCoreDataStack(context: context) 77 | } 78 | 79 | func test_individualStepMigration_2to3() { 80 | let sourceURL = FileManager.moveFileFromBundleToTempDirectory(filename: "CoreDataMigration_Example_2.sqlite") 81 | let toVersion = CoreDataMigrationVersion.version3 82 | 83 | sut.migrateStore(at: sourceURL, toVersion: toVersion) 84 | 85 | XCTAssertTrue(FileManager.default.fileExists(atPath: sourceURL.path)) 86 | 87 | let model = NSManagedObjectModel.managedObjectModel(forResource: toVersion.rawValue) 88 | let context = NSManagedObjectContext(model: model, storeURL: sourceURL) 89 | 90 | let postRequest = NSFetchRequest.init(entityName: "Post") 91 | let postSort = NSSortDescriptor(key: "postID", ascending: false) 92 | postRequest.sortDescriptors = [postSort] 93 | 94 | let migratedPosts = try? context.fetch(postRequest) 95 | 96 | XCTAssertEqual(migratedPosts?.count, 10) 97 | 98 | let firstMigratedPost = migratedPosts?.first 99 | 100 | let migratedDate = firstMigratedPost?.value(forKey: "date") as? Date 101 | let migratedPostID = firstMigratedPost?.value(forKey: "postID") as? String 102 | let migratedHexColor = firstMigratedPost?.value(forKey: "hexColor") as? String 103 | let migratedPostSections = firstMigratedPost?.value(forKey: "sections") as? Set 104 | 105 | XCTAssertEqual(migratedDate?.timeIntervalSince1970, 1547494150.058821) 106 | XCTAssertEqual(migratedPostID, "FFFECB21-6645-4FDD-B8B0-B960D0E61F5A") 107 | XCTAssertEqual(migratedHexColor, "1BB732") 108 | XCTAssertEqual(migratedPostSections?.count, 1) 109 | 110 | let migratedSection = migratedPostSections?.first 111 | 112 | let migratedBody = migratedSection?.value(forKey: "body") as? String 113 | let migratedIndex = migratedSection?.value(forKey: "index") as? Int 114 | let migratedTitle = migratedSection?.value(forKey: "title") as? String 115 | let migratedPost = migratedSection?.value(forKey: "post") as? NSManagedObject 116 | 117 | XCTAssertEqual(migratedTitle, "Test...") 118 | XCTAssertEqual(migratedBody, "Test body") 119 | XCTAssertEqual(migratedIndex, 0) 120 | 121 | XCTAssertNotNil(migratedPost) 122 | 123 | let contentRequest = NSFetchRequest.init(entityName: "Section") 124 | 125 | let migratedSections = try? context.fetch(contentRequest) 126 | 127 | XCTAssertEqual(migratedSections?.count, 10) 128 | 129 | tearDownCoreDataStack(context: context) 130 | } 131 | 132 | func test_individualStepMigration_3to4() { 133 | let sourceURL = FileManager.moveFileFromBundleToTempDirectory(filename: "CoreDataMigration_Example_3.sqlite") 134 | let toVersion = CoreDataMigrationVersion.version4 135 | 136 | sut.migrateStore(at: sourceURL, toVersion: toVersion) 137 | 138 | XCTAssertTrue(FileManager.default.fileExists(atPath: sourceURL.path)) 139 | 140 | let model = NSManagedObjectModel.managedObjectModel(forResource: toVersion.rawValue) 141 | let context = NSManagedObjectContext(model: model, storeURL: sourceURL) 142 | 143 | let postRequest = NSFetchRequest.init(entityName: "Post") 144 | let postSort = NSSortDescriptor(key: "date", ascending: false) 145 | postRequest.sortDescriptors = [postSort] 146 | 147 | let migratedPosts = try? context.fetch(postRequest) 148 | 149 | XCTAssertEqual(migratedPosts?.count, 12) 150 | 151 | let firstMigratedPost = migratedPosts?.first 152 | 153 | let migratedDate = firstMigratedPost?.value(forKey: "date") as? Date 154 | let migratedPostID = firstMigratedPost?.value(forKey: "postID") as? String 155 | let migratedSoftDeleted = firstMigratedPost?.value(forKey: "softDeleted") as? Bool 156 | let migratedHexColor = firstMigratedPost?.value(forKey: "hexColor") as? String 157 | let migratedPostSections = firstMigratedPost?.value(forKey: "sections") as? Set 158 | 159 | XCTAssertEqual(migratedDate?.timeIntervalSince1970, 1547764015.200076) 160 | XCTAssertEqual(migratedPostID, "7BC43935-3209-404E-836A-06F3C6518CB1") 161 | XCTAssertFalse(migratedSoftDeleted ?? true) 162 | XCTAssertEqual(migratedHexColor, "BAEFB6") 163 | XCTAssertEqual(migratedPostSections?.count, 2) 164 | 165 | let orderedMigratedSections = migratedPostSections?.sorted { 166 | let index1 = $0.value(forKey: "index") as! Int 167 | let index2 = $1.value(forKey: "index") as! Int 168 | return index1 < index2 169 | } 170 | 171 | let migratedSection = orderedMigratedSections?.first 172 | 173 | let migratedBody = migratedSection?.value(forKey: "body") as? String 174 | let migratedIndex = migratedSection?.value(forKey: "index") as? Int 175 | let migratedTitle = migratedSection?.value(forKey: "title") as? String 176 | let migratedPost = migratedSection?.value(forKey: "post") as? NSManagedObject 177 | 178 | let expectedBody = "Nam sapien nibh, ornare vitae cursus vehicula, pretium id nibh. Nunc nulla enim, mollis sed leo eu, maximus semper mi. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis faucibus, tortor vitae gravida tristique, dolor nibh faucibus lorem, at semper ex mi id felis. Mauris molestie mauris in feugiat aliquet. In quam ipsum, vestibulum ac purus eu, vulputate consectetur justo. Integer facilisis dictum risus, sit amet condimentum quam pulvinar nec. Curabitur blandit est ut libero lobortis malesuada. Morbi gravida arcu at ultrices commodo. Ut enim metus, viverra tincidunt turpis sit amet, consectetur pharetra urna. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse ex massa, aliquam at leo quis, rhoncus accumsan nisl. Donec id porttitor massa." 179 | 180 | XCTAssertEqual(migratedTitle, "This post will have 2 sections") 181 | XCTAssertEqual(migratedBody, expectedBody) 182 | XCTAssertEqual(migratedIndex, 0) 183 | 184 | XCTAssertNotNil(migratedPost) 185 | 186 | let contentRequest = NSFetchRequest.init(entityName: "Section") 187 | 188 | let migratedSections = try? context.fetch(contentRequest) 189 | 190 | XCTAssertEqual(migratedSections?.count, 14) 191 | 192 | tearDownCoreDataStack(context: context) 193 | } 194 | 195 | // MARK: MultipleStepMigrations 196 | 197 | func test_multipleStepMigration_fromVersion1toVersion4() { 198 | let sourceURL = FileManager.moveFileFromBundleToTempDirectory(filename: "CoreDataMigration_Example_1.sqlite") 199 | let toVersion = CoreDataMigrationVersion.version4 200 | 201 | sut.migrateStore(at: sourceURL, toVersion: toVersion) 202 | 203 | XCTAssertTrue(FileManager.default.fileExists(atPath: sourceURL.path)) 204 | 205 | let model = NSManagedObjectModel.managedObjectModel(forResource: toVersion.rawValue) 206 | let context = NSManagedObjectContext(model: model, storeURL: sourceURL) 207 | 208 | let postRequest = NSFetchRequest.init(entityName: "Post") 209 | let sectionRequest = NSFetchRequest.init(entityName: "Section") 210 | 211 | let migratedPosts = try? context.fetch(postRequest) 212 | let migratedSections = try? context.fetch(sectionRequest) 213 | 214 | XCTAssertEqual(migratedPosts?.count, 10) 215 | XCTAssertEqual(migratedSections?.count, 10) 216 | 217 | tearDownCoreDataStack(context: context) 218 | } 219 | 220 | // MARK: MigrationRequired 221 | 222 | func test_requiresMigration_fromVersion1ToCurrent_true() { 223 | let storeURL = FileManager.moveFileFromBundleToTempDirectory(filename: "CoreDataMigration_Example_1.sqlite") 224 | 225 | let requiresMigration = sut.requiresMigration(at: storeURL, toVersion: CoreDataMigrationVersion.current) 226 | 227 | XCTAssertTrue(requiresMigration) 228 | } 229 | 230 | func test_requiresMigration_fromVersion2ToVersion2_false() { 231 | let storeURL = FileManager.moveFileFromBundleToTempDirectory(filename: "CoreDataMigration_Example_2.sqlite") 232 | 233 | let requiresMigration = sut.requiresMigration(at: storeURL, toVersion: .version2) 234 | 235 | XCTAssertFalse(requiresMigration) 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /CoreDataMigration-ExampleTests/Tests/CoreData/Migration/Models/CoreDataMigration_Example_1.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wibosco/CoreDataMigrationRevised-Example/2f91699fd6c027f0461307dcf201010425b0a891/CoreDataMigration-ExampleTests/Tests/CoreData/Migration/Models/CoreDataMigration_Example_1.sqlite -------------------------------------------------------------------------------- /CoreDataMigration-ExampleTests/Tests/CoreData/Migration/Models/CoreDataMigration_Example_2.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wibosco/CoreDataMigrationRevised-Example/2f91699fd6c027f0461307dcf201010425b0a891/CoreDataMigration-ExampleTests/Tests/CoreData/Migration/Models/CoreDataMigration_Example_2.sqlite -------------------------------------------------------------------------------- /CoreDataMigration-ExampleTests/Tests/CoreData/Migration/Models/CoreDataMigration_Example_3.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wibosco/CoreDataMigrationRevised-Example/2f91699fd6c027f0461307dcf201010425b0a891/CoreDataMigration-ExampleTests/Tests/CoreData/Migration/Models/CoreDataMigration_Example_3.sqlite -------------------------------------------------------------------------------- /CoreDataMigration-ExampleTests/Tests/Mocks/MockCoreDataMigrator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockCoreDataMigrator.swift 3 | // CoreDataMigration-ExampleTests 4 | // 5 | // Created by William Boles on 02/01/2019. 6 | // Copyright © 2017 William Boles. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | @testable import CoreDataMigration_Example 13 | 14 | class MockCoreDataMigrator: CoreDataMigratorProtocol { 15 | 16 | var requiresMigrationWasCalled = false 17 | var migrateStoreWasCalled = false 18 | 19 | var requiresMigrationToBeReturned = false 20 | 21 | // MARK: - CoreDataMigratorProtocol 22 | 23 | func requiresMigration(at: URL, toVersion: CoreDataMigrationVersion) -> Bool { 24 | requiresMigrationWasCalled = true 25 | 26 | return requiresMigrationToBeReturned 27 | } 28 | 29 | func migrateStore(at storeURL: URL, toVersion version: CoreDataMigrationVersion) { 30 | migrateStoreWasCalled = true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane", "2.104.0" 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.0) 5 | addressable (2.5.2) 6 | public_suffix (>= 2.0.2, < 4.0) 7 | atomos (0.1.3) 8 | babosa (1.0.2) 9 | claide (1.0.2) 10 | colored (1.2) 11 | colored2 (3.1.2) 12 | commander-fastlane (4.4.6) 13 | highline (~> 1.7.2) 14 | declarative (0.0.10) 15 | declarative-option (0.1.0) 16 | domain_name (0.5.20180417) 17 | unf (>= 0.0.5, < 1.0.0) 18 | dotenv (2.5.0) 19 | emoji_regex (0.1.1) 20 | excon (0.62.0) 21 | faraday (0.15.4) 22 | multipart-post (>= 1.2, < 3) 23 | faraday-cookie_jar (0.0.6) 24 | faraday (>= 0.7.4) 25 | http-cookie (~> 1.0.0) 26 | faraday_middleware (0.12.2) 27 | faraday (>= 0.7.4, < 1.0) 28 | fastimage (2.1.5) 29 | fastlane (2.104.0) 30 | CFPropertyList (>= 2.3, < 4.0.0) 31 | addressable (>= 2.3, < 3.0.0) 32 | babosa (>= 1.0.2, < 2.0.0) 33 | bundler (>= 1.12.0, < 2.0.0) 34 | colored 35 | commander-fastlane (>= 4.4.6, < 5.0.0) 36 | dotenv (>= 2.1.1, < 3.0.0) 37 | emoji_regex (~> 0.1) 38 | excon (>= 0.45.0, < 1.0.0) 39 | faraday (~> 0.9) 40 | faraday-cookie_jar (~> 0.0.6) 41 | faraday_middleware (~> 0.9) 42 | fastimage (>= 2.1.0, < 3.0.0) 43 | gh_inspector (>= 1.1.2, < 2.0.0) 44 | google-api-client (>= 0.21.2, < 0.24.0) 45 | highline (>= 1.7.2, < 2.0.0) 46 | json (< 3.0.0) 47 | mini_magick (~> 4.5.1) 48 | multi_json 49 | multi_xml (~> 0.5) 50 | multipart-post (~> 2.0.0) 51 | plist (>= 3.1.0, < 4.0.0) 52 | public_suffix (~> 2.0.0) 53 | rubyzip (>= 1.2.2, < 2.0.0) 54 | security (= 0.1.3) 55 | simctl (~> 1.6.3) 56 | slack-notifier (>= 2.0.0, < 3.0.0) 57 | terminal-notifier (>= 1.6.2, < 2.0.0) 58 | terminal-table (>= 1.4.5, < 2.0.0) 59 | tty-screen (>= 0.6.3, < 1.0.0) 60 | tty-spinner (>= 0.8.0, < 1.0.0) 61 | word_wrap (~> 1.0.0) 62 | xcodeproj (>= 1.6.0, < 2.0.0) 63 | xcpretty (~> 0.3.0) 64 | xcpretty-travis-formatter (>= 0.0.3) 65 | gh_inspector (1.1.3) 66 | google-api-client (0.23.9) 67 | addressable (~> 2.5, >= 2.5.1) 68 | googleauth (>= 0.5, < 0.7.0) 69 | httpclient (>= 2.8.1, < 3.0) 70 | mime-types (~> 3.0) 71 | representable (~> 3.0) 72 | retriable (>= 2.0, < 4.0) 73 | signet (~> 0.9) 74 | googleauth (0.6.7) 75 | faraday (~> 0.12) 76 | jwt (>= 1.4, < 3.0) 77 | memoist (~> 0.16) 78 | multi_json (~> 1.11) 79 | os (>= 0.9, < 2.0) 80 | signet (~> 0.7) 81 | highline (1.7.10) 82 | http-cookie (1.0.3) 83 | domain_name (~> 0.5) 84 | httpclient (2.8.3) 85 | json (2.1.0) 86 | jwt (1.5.6) 87 | memoist (0.16.0) 88 | mime-types (3.2.2) 89 | mime-types-data (~> 3.2015) 90 | mime-types-data (3.2018.0812) 91 | mini_magick (4.5.1) 92 | multi_json (1.13.1) 93 | multi_xml (0.6.0) 94 | multipart-post (2.0.0) 95 | nanaimo (0.2.6) 96 | naturally (2.2.0) 97 | os (1.0.0) 98 | plist (3.5.0) 99 | public_suffix (2.0.5) 100 | representable (3.0.4) 101 | declarative (< 0.1.0) 102 | declarative-option (< 0.2.0) 103 | uber (< 0.2.0) 104 | retriable (3.1.2) 105 | rouge (2.0.7) 106 | rubyzip (1.2.2) 107 | security (0.1.3) 108 | signet (0.11.0) 109 | addressable (~> 2.3) 110 | faraday (~> 0.9) 111 | jwt (>= 1.5, < 3.0) 112 | multi_json (~> 1.10) 113 | simctl (1.6.5) 114 | CFPropertyList 115 | naturally 116 | slack-notifier (2.3.2) 117 | terminal-notifier (1.8.0) 118 | terminal-table (1.8.0) 119 | unicode-display_width (~> 1.1, >= 1.1.1) 120 | tty-cursor (0.6.0) 121 | tty-screen (0.6.5) 122 | tty-spinner (0.9.0) 123 | tty-cursor (~> 0.6.0) 124 | uber (0.1.0) 125 | unf (0.1.4) 126 | unf_ext 127 | unf_ext (0.0.7.5) 128 | unicode-display_width (1.4.1) 129 | word_wrap (1.0.0) 130 | xcodeproj (1.7.0) 131 | CFPropertyList (>= 2.3.3, < 4.0) 132 | atomos (~> 0.1.3) 133 | claide (>= 1.0.2, < 2.0) 134 | colored2 (~> 3.1) 135 | nanaimo (~> 0.2.6) 136 | xcpretty (0.3.0) 137 | rouge (~> 2.0.7) 138 | xcpretty-travis-formatter (1.0.0) 139 | xcpretty (~> 0.2, >= 0.0.7) 140 | 141 | PLATFORMS 142 | ruby 143 | 144 | DEPENDENCIES 145 | fastlane (= 2.104.0) 146 | 147 | BUNDLED WITH 148 | 1.16.0 149 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 William Boles 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/wibosco/CoreDataMigrationRevised-Example.svg)](https://travis-ci.org/wibosco/CoreDataMigrationRevised-Example) 2 | Swift 3 | Twitter: @wibosco 4 | 5 | # CoreDataMigration-Example 6 | An example project showing how we can implement progressive Core Data migrations as shown in this article - https://williamboles.com/progressive-core-data-migration/ 7 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | fastlane_version "2.56.0" 2 | default_platform :ios 3 | 4 | platform :ios do 5 | 6 | # Common 7 | 8 | lane :run_unit_tests do 9 | scan( 10 | scheme: 'CoreDataMigration-Example', 11 | skip_build: true 12 | ) 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew cask install fastlane` 16 | 17 | # Available Actions 18 | ## iOS 19 | ### ios run_unit_tests 20 | ``` 21 | fastlane ios run_unit_tests 22 | ``` 23 | 24 | 25 | ---- 26 | 27 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 28 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 29 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 30 | --------------------------------------------------------------------------------