├── .gitignore ├── .swift-version ├── .travis.yml ├── CoreDataKit.podspec ├── CoreDataKit.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ ├── xcbaselines │ └── D5D7C6C61958B8CF0048B576.xcbaseline │ │ ├── 02871957-D92B-473A-9736-E9785F6161D8.plist │ │ └── Info.plist │ └── xcschemes │ └── CoreDataKit.xcscheme ├── CoreDataKit ├── CDK.swift ├── CDKDebugger.m ├── CoreDataStack.swift ├── Importing │ ├── CoreDataStack+Importing.swift │ ├── JsonDecode+Importing.swift │ ├── NSAttributeDescription+Importing.swift │ ├── NSEntityDescription+Importing.swift │ ├── NSManagedObject+Importing.swift │ ├── NSManagedObjectContext+Importing.swift │ ├── NSPropertyDescription+Importing.swift │ ├── NSRelationshipDescription+Importing.swift │ └── Types+Importing.swift ├── Info.plist ├── ManagedObjectObserver.swift ├── NSManagedObjectContext.swift ├── NSPersistentStore.swift ├── NSPersistentStoreCoordinator.swift ├── NamedManagedObject.swift ├── TableViewFetchedResultsControllerDelegate.swift └── Types.swift ├── CoreDataKitTests ├── CoreDataKitTests.swift ├── CoreDataStackTests.swift ├── Fixtures │ ├── Car.swift │ ├── Employee.swift │ ├── EmployeeImportable.swift │ ├── EmployeeIncorrectEntityName.swift │ ├── EmployeeWithRelationEmbedding.swift │ ├── EmployeeWithRelations.swift │ ├── Employees.json │ ├── EmployeesNestedEmbeddingRelation.json │ ├── EmployeesNestedRelation.json │ ├── EmployeesReferencedRelation.json │ ├── EmployeesWithNull.json │ ├── EmployeesWithOmittedField.json │ ├── Model.xcdatamodeld │ │ └── Model.xcdatamodel │ │ │ └── contents │ └── Salary.swift ├── Importing │ ├── CoreDataStack+ImportingTests.swift │ └── NSManagedObject+ImportingTests.swift ├── Info.plist ├── ManagedObjectObserverTests.swift ├── NSManagedObjectContextTests.swift ├── NSPersistentStoreCoordinatorTests.swift ├── NSPersistentStoreTests.swift └── TestCase.swift ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | 23 | /Pods/ 24 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | before_install: 3 | - gem install cocoapods -v '0.33.1' 4 | - brew update 5 | - brew install xctool 6 | before_script: export LANG=en_US.UTF-8 7 | script: xctool test -workspace CoreDataKit.xcworkspace -scheme CoreDataKit -sdk iphonesimulator -destination 'name=iPhone Retina (3.5-inch),OS=7.1' ONLY_ACTIVE_ARCH=NO 8 | -------------------------------------------------------------------------------- /CoreDataKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CoreDataKit" 3 | s.version = "0.13.1" 4 | s.license = "MIT" 5 | 6 | s.summary = "CoreDataKit makes common operations on objects and importing into CoreData a breeze." 7 | 8 | s.description = <<-DESC 9 | CoreDataKit takes care of the hard and verbose parts of CoreData. It manages child contexts for you and helps to easily fetch, create and delete objects. 10 | DESC 11 | 12 | s.authors = { "Mathijs Kadijk" => "mkadijk@gmail.com" } 13 | s.social_media_url = "http://twitter.com/mac_cain13" 14 | s.homepage = "https://github.com/mac-cain13/CoreDataKit" 15 | 16 | s.source = { :git => "https://github.com/mac-cain13/CoreDataKit.git", :tag => s.version } 17 | s.platform = :ios, "8.0" 18 | s.requires_arc = true 19 | s.source_files = "CoreDataKit/**/*.swift" 20 | 21 | end 22 | -------------------------------------------------------------------------------- /CoreDataKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D50881A51A0ABF95007CA193 /* CoreDataStack+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D50881A41A0ABF95007CA193 /* CoreDataStack+Importing.swift */; }; 11 | D50881A71A0AC2CD007CA193 /* CoreDataStack+ImportingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D50881A61A0AC2CD007CA193 /* CoreDataStack+ImportingTests.swift */; }; 12 | D5093C6A19EF9B5700139262 /* NSPersistentStoreCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5093C6919EF9B5700139262 /* NSPersistentStoreCoordinatorTests.swift */; }; 13 | D5098CDD1A052D9F000CC246 /* ManagedObjectObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5098CDC1A052D9F000CC246 /* ManagedObjectObserverTests.swift */; }; 14 | D51AFE161965266000287A2D /* NSPersistentStoreCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D51AFE151965266000287A2D /* NSPersistentStoreCoordinator.swift */; }; 15 | D5253DA119F12F1C00E8B4AF /* NSManagedObject+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5253DA019F12F1C00E8B4AF /* NSManagedObject+Importing.swift */; }; 16 | D5253DA319F1398600E8B4AF /* NSManagedObjectContext+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5253DA219F1398600E8B4AF /* NSManagedObjectContext+Importing.swift */; }; 17 | D5253DA519F1491E00E8B4AF /* NamedManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5253DA419F1491E00E8B4AF /* NamedManagedObject.swift */; }; 18 | D5253DA719F1591000E8B4AF /* EmployeeIncorrectEntityName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5253DA619F1591000E8B4AF /* EmployeeIncorrectEntityName.swift */; }; 19 | D532100E19F8D63D007C9DCD /* EmployeesWithNull.json in Resources */ = {isa = PBXBuildFile; fileRef = D532100D19F8D63D007C9DCD /* EmployeesWithNull.json */; }; 20 | D532101019F8DA4D007C9DCD /* EmployeesWithOmittedField.json in Resources */ = {isa = PBXBuildFile; fileRef = D532100F19F8DA4D007C9DCD /* EmployeesWithOmittedField.json */; }; 21 | D540D82C19FE2A5E00769B84 /* Types+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D540D82B19FE2A5E00769B84 /* Types+Importing.swift */; }; 22 | D55152D1196A739500857453 /* NSPersistentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55152D0196A739500857453 /* NSPersistentStore.swift */; }; 23 | D553FD971959505800E71FCE /* NSManagedObjectContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553FD961959505800E71FCE /* NSManagedObjectContext.swift */; }; 24 | D556DF8D19EF8C71008556A8 /* CoreDataStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D556DF8C19EF8C71008556A8 /* CoreDataStackTests.swift */; }; 25 | D566F6AE19F0DCA0003C503F /* NSPropertyDescription+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D566F6AD19F0DCA0003C503F /* NSPropertyDescription+Importing.swift */; }; 26 | D566F6B219F0F277003C503F /* JsonDecode+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D566F6B119F0F277003C503F /* JsonDecode+Importing.swift */; }; 27 | D56E1EAA19FF7409006A280C /* EmployeesNestedEmbeddingRelation.json in Resources */ = {isa = PBXBuildFile; fileRef = D56E1EA919FF7409006A280C /* EmployeesNestedEmbeddingRelation.json */; }; 28 | D56E1EAC19FF749B006A280C /* EmployeeWithRelationEmbedding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56E1EAB19FF749B006A280C /* EmployeeWithRelationEmbedding.swift */; }; 29 | D56E1EAE19FF749B006A280C /* Salary.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56E1EAD19FF749B006A280C /* Salary.swift */; }; 30 | D58D29D919F83604006D3B07 /* EmployeesNestedRelation.json in Resources */ = {isa = PBXBuildFile; fileRef = D58D29D819F83604006D3B07 /* EmployeesNestedRelation.json */; }; 31 | D595F67F19EEDE6000D73790 /* Employee.swift in Sources */ = {isa = PBXBuildFile; fileRef = D595F67E19EEDE6000D73790 /* Employee.swift */; }; 32 | D595F68119EEE17B00D73790 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D595F67B19EEDE3100D73790 /* Model.xcdatamodeld */; }; 33 | D595F68319EEE23000D73790 /* TestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D595F68219EEE23000D73790 /* TestCase.swift */; }; 34 | D595F68519EF0EA100D73790 /* CoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D595F68419EF0EA100D73790 /* CoreDataStack.swift */; }; 35 | D595F68719EF0ED800D73790 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D595F68619EF0ED800D73790 /* Types.swift */; }; 36 | D5B474B819F012F9004132F8 /* NSEntityDescription+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B474B719F012F9004132F8 /* NSEntityDescription+Importing.swift */; }; 37 | D5B474BA19F0181F004132F8 /* NSAttributeDescription+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B474B919F0181F004132F8 /* NSAttributeDescription+Importing.swift */; }; 38 | D5BD629F1A01587800098343 /* TableViewFetchedResultsControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5BD629E1A01587800098343 /* TableViewFetchedResultsControllerDelegate.swift */; }; 39 | D5C4B96719FEBB390058F0AC /* NSRelationshipDescription+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C4B96619FEBB390058F0AC /* NSRelationshipDescription+Importing.swift */; }; 40 | D5C6C63F19EEC6D60035BFFF /* NSManagedObjectContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C6C63E19EEC6D60035BFFF /* NSManagedObjectContextTests.swift */; }; 41 | D5D7C6C81958B8CF0048B576 /* CoreDataKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5D7C6BC1958B8CF0048B576 /* CoreDataKit.framework */; }; 42 | D5D7C6CF1958B8CF0048B576 /* CoreDataKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D7C6CE1958B8CF0048B576 /* CoreDataKitTests.swift */; }; 43 | D5D7C6D91958B9300048B576 /* CDK.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D7C6D81958B9300048B576 /* CDK.swift */; }; 44 | D5D8E81C1A0363A300991FEA /* ManagedObjectObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D8E81B1A0363A300991FEA /* ManagedObjectObserver.swift */; }; 45 | D5EDACE319EF950B00FAC712 /* NSPersistentStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5EDACE219EF950B00FAC712 /* NSPersistentStoreTests.swift */; }; 46 | D5FE0FBE19F28340008DC1ED /* Employees.json in Resources */ = {isa = PBXBuildFile; fileRef = D5FE0FBD19F28340008DC1ED /* Employees.json */; }; 47 | D5FE0FC019F286DF008DC1ED /* NSManagedObject+ImportingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FE0FBF19F286DF008DC1ED /* NSManagedObject+ImportingTests.swift */; }; 48 | D5FE0FC419F29FFD008DC1ED /* EmployeeImportable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FE0FC319F29FFD008DC1ED /* EmployeeImportable.swift */; }; 49 | D5FE0FC619F4E0D3008DC1ED /* Car.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FE0FC519F4E0D3008DC1ED /* Car.swift */; }; 50 | D5FE0FC819F4E0D4008DC1ED /* EmployeeWithRelations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FE0FC719F4E0D4008DC1ED /* EmployeeWithRelations.swift */; }; 51 | D5FE0FCC19F4EAC6008DC1ED /* EmployeesReferencedRelation.json in Resources */ = {isa = PBXBuildFile; fileRef = D5FE0FCB19F4EAC6008DC1ED /* EmployeesReferencedRelation.json */; }; 52 | /* End PBXBuildFile section */ 53 | 54 | /* Begin PBXContainerItemProxy section */ 55 | D5D7C6C91958B8CF0048B576 /* PBXContainerItemProxy */ = { 56 | isa = PBXContainerItemProxy; 57 | containerPortal = D5D7C6B31958B8CF0048B576 /* Project object */; 58 | proxyType = 1; 59 | remoteGlobalIDString = D5D7C6BB1958B8CF0048B576; 60 | remoteInfo = CoreDataKit; 61 | }; 62 | /* End PBXContainerItemProxy section */ 63 | 64 | /* Begin PBXFileReference section */ 65 | D50881A41A0ABF95007CA193 /* CoreDataStack+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreDataStack+Importing.swift"; sourceTree = ""; }; 66 | D50881A61A0AC2CD007CA193 /* CoreDataStack+ImportingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreDataStack+ImportingTests.swift"; sourceTree = ""; }; 67 | D5093C6919EF9B5700139262 /* NSPersistentStoreCoordinatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSPersistentStoreCoordinatorTests.swift; sourceTree = ""; }; 68 | D5098CDC1A052D9F000CC246 /* ManagedObjectObserverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectObserverTests.swift; sourceTree = ""; }; 69 | D51AFE151965266000287A2D /* NSPersistentStoreCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSPersistentStoreCoordinator.swift; sourceTree = ""; }; 70 | D5253DA019F12F1C00E8B4AF /* NSManagedObject+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "NSManagedObject+Importing.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 71 | D5253DA219F1398600E8B4AF /* NSManagedObjectContext+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Importing.swift"; sourceTree = ""; }; 72 | D5253DA419F1491E00E8B4AF /* NamedManagedObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NamedManagedObject.swift; sourceTree = ""; }; 73 | D5253DA619F1591000E8B4AF /* EmployeeIncorrectEntityName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmployeeIncorrectEntityName.swift; sourceTree = ""; }; 74 | D532100D19F8D63D007C9DCD /* EmployeesWithNull.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = EmployeesWithNull.json; sourceTree = ""; }; 75 | D532100F19F8DA4D007C9DCD /* EmployeesWithOmittedField.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = EmployeesWithOmittedField.json; sourceTree = ""; }; 76 | D540D82B19FE2A5E00769B84 /* Types+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "Types+Importing.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 77 | D55152D0196A739500857453 /* NSPersistentStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSPersistentStore.swift; sourceTree = ""; }; 78 | D553FD961959505800E71FCE /* NSManagedObjectContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSManagedObjectContext.swift; sourceTree = ""; }; 79 | D556DF8C19EF8C71008556A8 /* CoreDataStackTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataStackTests.swift; sourceTree = ""; }; 80 | D566F6AD19F0DCA0003C503F /* NSPropertyDescription+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSPropertyDescription+Importing.swift"; sourceTree = ""; }; 81 | D566F6B119F0F277003C503F /* JsonDecode+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "JsonDecode+Importing.swift"; sourceTree = ""; }; 82 | D56E1EA919FF7409006A280C /* EmployeesNestedEmbeddingRelation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = EmployeesNestedEmbeddingRelation.json; sourceTree = ""; }; 83 | D56E1EAB19FF749B006A280C /* EmployeeWithRelationEmbedding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = EmployeeWithRelationEmbedding.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 84 | D56E1EAD19FF749B006A280C /* Salary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Salary.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 85 | D58D29D819F83604006D3B07 /* EmployeesNestedRelation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = EmployeesNestedRelation.json; sourceTree = ""; }; 86 | D595F67C19EEDE3100D73790 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; 87 | D595F67E19EEDE6000D73790 /* Employee.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Employee.swift; sourceTree = ""; }; 88 | D595F68219EEE23000D73790 /* TestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCase.swift; sourceTree = ""; }; 89 | D595F68419EF0EA100D73790 /* CoreDataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataStack.swift; sourceTree = ""; }; 90 | D595F68619EF0ED800D73790 /* Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = ""; }; 91 | D5B474B719F012F9004132F8 /* NSEntityDescription+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSEntityDescription+Importing.swift"; sourceTree = ""; }; 92 | D5B474B919F0181F004132F8 /* NSAttributeDescription+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributeDescription+Importing.swift"; sourceTree = ""; }; 93 | D5BD629E1A01587800098343 /* TableViewFetchedResultsControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewFetchedResultsControllerDelegate.swift; sourceTree = ""; }; 94 | D5C4B96619FEBB390058F0AC /* NSRelationshipDescription+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSRelationshipDescription+Importing.swift"; sourceTree = ""; }; 95 | D5C6C63E19EEC6D60035BFFF /* NSManagedObjectContextTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSManagedObjectContextTests.swift; sourceTree = ""; }; 96 | D5D7C6BC1958B8CF0048B576 /* CoreDataKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreDataKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 97 | D5D7C6C01958B8CF0048B576 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 98 | D5D7C6C71958B8CF0048B576 /* CoreDataKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreDataKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 99 | D5D7C6CD1958B8CF0048B576 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 100 | D5D7C6CE1958B8CF0048B576 /* CoreDataKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataKitTests.swift; sourceTree = ""; }; 101 | D5D7C6D81958B9300048B576 /* CDK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CDK.swift; sourceTree = ""; }; 102 | D5D8E81B1A0363A300991FEA /* ManagedObjectObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectObserver.swift; sourceTree = ""; }; 103 | D5EDACE219EF950B00FAC712 /* NSPersistentStoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSPersistentStoreTests.swift; sourceTree = ""; }; 104 | D5FE0FBD19F28340008DC1ED /* Employees.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Employees.json; sourceTree = ""; }; 105 | D5FE0FBF19F286DF008DC1ED /* NSManagedObject+ImportingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "NSManagedObject+ImportingTests.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 106 | D5FE0FC319F29FFD008DC1ED /* EmployeeImportable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmployeeImportable.swift; sourceTree = ""; }; 107 | D5FE0FC519F4E0D3008DC1ED /* Car.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Car.swift; sourceTree = ""; }; 108 | D5FE0FC719F4E0D4008DC1ED /* EmployeeWithRelations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmployeeWithRelations.swift; sourceTree = ""; }; 109 | D5FE0FCB19F4EAC6008DC1ED /* EmployeesReferencedRelation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = EmployeesReferencedRelation.json; sourceTree = ""; }; 110 | /* End PBXFileReference section */ 111 | 112 | /* Begin PBXFrameworksBuildPhase section */ 113 | D5D7C6B81958B8CF0048B576 /* Frameworks */ = { 114 | isa = PBXFrameworksBuildPhase; 115 | buildActionMask = 2147483647; 116 | files = ( 117 | ); 118 | runOnlyForDeploymentPostprocessing = 0; 119 | }; 120 | D5D7C6C41958B8CF0048B576 /* Frameworks */ = { 121 | isa = PBXFrameworksBuildPhase; 122 | buildActionMask = 2147483647; 123 | files = ( 124 | D5D7C6C81958B8CF0048B576 /* CoreDataKit.framework in Frameworks */, 125 | ); 126 | runOnlyForDeploymentPostprocessing = 0; 127 | }; 128 | /* End PBXFrameworksBuildPhase section */ 129 | 130 | /* Begin PBXGroup section */ 131 | D5090B3C19EDB8AA000D66C5 /* Importing */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | D50881A41A0ABF95007CA193 /* CoreDataStack+Importing.swift */, 135 | D540D82B19FE2A5E00769B84 /* Types+Importing.swift */, 136 | D566F6B119F0F277003C503F /* JsonDecode+Importing.swift */, 137 | D5B474B919F0181F004132F8 /* NSAttributeDescription+Importing.swift */, 138 | D5B474B719F012F9004132F8 /* NSEntityDescription+Importing.swift */, 139 | D5253DA019F12F1C00E8B4AF /* NSManagedObject+Importing.swift */, 140 | D5253DA219F1398600E8B4AF /* NSManagedObjectContext+Importing.swift */, 141 | D566F6AD19F0DCA0003C503F /* NSPropertyDescription+Importing.swift */, 142 | D5C4B96619FEBB390058F0AC /* NSRelationshipDescription+Importing.swift */, 143 | ); 144 | path = Importing; 145 | sourceTree = ""; 146 | }; 147 | D595F67A19EEDD8800D73790 /* Fixtures */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | D595F67B19EEDE3100D73790 /* Model.xcdatamodeld */, 151 | D5FE0FC519F4E0D3008DC1ED /* Car.swift */, 152 | D56E1EAD19FF749B006A280C /* Salary.swift */, 153 | D595F67E19EEDE6000D73790 /* Employee.swift */, 154 | D5FE0FC319F29FFD008DC1ED /* EmployeeImportable.swift */, 155 | D5253DA619F1591000E8B4AF /* EmployeeIncorrectEntityName.swift */, 156 | D5FE0FC719F4E0D4008DC1ED /* EmployeeWithRelations.swift */, 157 | D56E1EAB19FF749B006A280C /* EmployeeWithRelationEmbedding.swift */, 158 | D5FE0FBD19F28340008DC1ED /* Employees.json */, 159 | D532100D19F8D63D007C9DCD /* EmployeesWithNull.json */, 160 | D532100F19F8DA4D007C9DCD /* EmployeesWithOmittedField.json */, 161 | D58D29D819F83604006D3B07 /* EmployeesNestedRelation.json */, 162 | D5FE0FCB19F4EAC6008DC1ED /* EmployeesReferencedRelation.json */, 163 | D56E1EA919FF7409006A280C /* EmployeesNestedEmbeddingRelation.json */, 164 | ); 165 | path = Fixtures; 166 | sourceTree = ""; 167 | }; 168 | D5D7C6B21958B8CF0048B576 = { 169 | isa = PBXGroup; 170 | children = ( 171 | D5D7C6BE1958B8CF0048B576 /* CoreDataKit */, 172 | D5D7C6CB1958B8CF0048B576 /* CoreDataKitTests */, 173 | D5D7C6BD1958B8CF0048B576 /* Products */, 174 | ); 175 | sourceTree = ""; 176 | }; 177 | D5D7C6BD1958B8CF0048B576 /* Products */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | D5D7C6BC1958B8CF0048B576 /* CoreDataKit.framework */, 181 | D5D7C6C71958B8CF0048B576 /* CoreDataKitTests.xctest */, 182 | ); 183 | name = Products; 184 | sourceTree = ""; 185 | }; 186 | D5D7C6BE1958B8CF0048B576 /* CoreDataKit */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | D595F68619EF0ED800D73790 /* Types.swift */, 190 | D5D7C6D81958B9300048B576 /* CDK.swift */, 191 | D595F68419EF0EA100D73790 /* CoreDataStack.swift */, 192 | D5D8E81B1A0363A300991FEA /* ManagedObjectObserver.swift */, 193 | D5253DA419F1491E00E8B4AF /* NamedManagedObject.swift */, 194 | D553FD961959505800E71FCE /* NSManagedObjectContext.swift */, 195 | D55152D0196A739500857453 /* NSPersistentStore.swift */, 196 | D51AFE151965266000287A2D /* NSPersistentStoreCoordinator.swift */, 197 | D5BD629E1A01587800098343 /* TableViewFetchedResultsControllerDelegate.swift */, 198 | D5090B3C19EDB8AA000D66C5 /* Importing */, 199 | D5D7C6BF1958B8CF0048B576 /* Supporting Files */, 200 | ); 201 | path = CoreDataKit; 202 | sourceTree = ""; 203 | }; 204 | D5D7C6BF1958B8CF0048B576 /* Supporting Files */ = { 205 | isa = PBXGroup; 206 | children = ( 207 | D5D7C6C01958B8CF0048B576 /* Info.plist */, 208 | ); 209 | name = "Supporting Files"; 210 | sourceTree = ""; 211 | }; 212 | D5D7C6CB1958B8CF0048B576 /* CoreDataKitTests */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | D595F68219EEE23000D73790 /* TestCase.swift */, 216 | D5D7C6CE1958B8CF0048B576 /* CoreDataKitTests.swift */, 217 | D556DF8C19EF8C71008556A8 /* CoreDataStackTests.swift */, 218 | D5098CDC1A052D9F000CC246 /* ManagedObjectObserverTests.swift */, 219 | D5C6C63E19EEC6D60035BFFF /* NSManagedObjectContextTests.swift */, 220 | D5EDACE219EF950B00FAC712 /* NSPersistentStoreTests.swift */, 221 | D5093C6919EF9B5700139262 /* NSPersistentStoreCoordinatorTests.swift */, 222 | D5FD050419EFF68200AC0F9F /* Importing */, 223 | D595F67A19EEDD8800D73790 /* Fixtures */, 224 | D5D7C6CC1958B8CF0048B576 /* Supporting Files */, 225 | ); 226 | path = CoreDataKitTests; 227 | sourceTree = ""; 228 | }; 229 | D5D7C6CC1958B8CF0048B576 /* Supporting Files */ = { 230 | isa = PBXGroup; 231 | children = ( 232 | D5D7C6CD1958B8CF0048B576 /* Info.plist */, 233 | ); 234 | name = "Supporting Files"; 235 | sourceTree = ""; 236 | }; 237 | D5FD050419EFF68200AC0F9F /* Importing */ = { 238 | isa = PBXGroup; 239 | children = ( 240 | D50881A61A0AC2CD007CA193 /* CoreDataStack+ImportingTests.swift */, 241 | D5FE0FBF19F286DF008DC1ED /* NSManagedObject+ImportingTests.swift */, 242 | ); 243 | path = Importing; 244 | sourceTree = ""; 245 | }; 246 | /* End PBXGroup section */ 247 | 248 | /* Begin PBXHeadersBuildPhase section */ 249 | D5D7C6B91958B8CF0048B576 /* Headers */ = { 250 | isa = PBXHeadersBuildPhase; 251 | buildActionMask = 2147483647; 252 | files = ( 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | }; 256 | /* End PBXHeadersBuildPhase section */ 257 | 258 | /* Begin PBXNativeTarget section */ 259 | D5D7C6BB1958B8CF0048B576 /* CoreDataKit */ = { 260 | isa = PBXNativeTarget; 261 | buildConfigurationList = D5D7C6D21958B8CF0048B576 /* Build configuration list for PBXNativeTarget "CoreDataKit" */; 262 | buildPhases = ( 263 | D5D7C6B71958B8CF0048B576 /* Sources */, 264 | D5D7C6B81958B8CF0048B576 /* Frameworks */, 265 | D5D7C6B91958B8CF0048B576 /* Headers */, 266 | D5D7C6BA1958B8CF0048B576 /* Resources */, 267 | ); 268 | buildRules = ( 269 | ); 270 | dependencies = ( 271 | ); 272 | name = CoreDataKit; 273 | productName = CoreDataKit; 274 | productReference = D5D7C6BC1958B8CF0048B576 /* CoreDataKit.framework */; 275 | productType = "com.apple.product-type.framework"; 276 | }; 277 | D5D7C6C61958B8CF0048B576 /* CoreDataKitTests */ = { 278 | isa = PBXNativeTarget; 279 | buildConfigurationList = D5D7C6D51958B8CF0048B576 /* Build configuration list for PBXNativeTarget "CoreDataKitTests" */; 280 | buildPhases = ( 281 | D5D7C6C31958B8CF0048B576 /* Sources */, 282 | D5D7C6C41958B8CF0048B576 /* Frameworks */, 283 | D5D7C6C51958B8CF0048B576 /* Resources */, 284 | ); 285 | buildRules = ( 286 | ); 287 | dependencies = ( 288 | D5D7C6CA1958B8CF0048B576 /* PBXTargetDependency */, 289 | ); 290 | name = CoreDataKitTests; 291 | productName = CoreDataKitTests; 292 | productReference = D5D7C6C71958B8CF0048B576 /* CoreDataKitTests.xctest */; 293 | productType = "com.apple.product-type.bundle.unit-test"; 294 | }; 295 | /* End PBXNativeTarget section */ 296 | 297 | /* Begin PBXProject section */ 298 | D5D7C6B31958B8CF0048B576 /* Project object */ = { 299 | isa = PBXProject; 300 | attributes = { 301 | LastSwiftMigration = 0700; 302 | LastSwiftUpdateCheck = 0700; 303 | LastUpgradeCheck = 0910; 304 | ORGANIZATIONNAME = "Mathijs Kadijk"; 305 | TargetAttributes = { 306 | D5D7C6BB1958B8CF0048B576 = { 307 | CreatedOnToolsVersion = 6.0; 308 | LastSwiftMigration = 0910; 309 | }; 310 | D5D7C6C61958B8CF0048B576 = { 311 | CreatedOnToolsVersion = 6.0; 312 | DevelopmentTeam = LC3Y92HQQ3; 313 | LastSwiftMigration = 0910; 314 | ProvisioningStyle = Automatic; 315 | TestTargetID = D5D7C6BB1958B8CF0048B576; 316 | }; 317 | }; 318 | }; 319 | buildConfigurationList = D5D7C6B61958B8CF0048B576 /* Build configuration list for PBXProject "CoreDataKit" */; 320 | compatibilityVersion = "Xcode 3.2"; 321 | developmentRegion = English; 322 | hasScannedForEncodings = 0; 323 | knownRegions = ( 324 | en, 325 | ); 326 | mainGroup = D5D7C6B21958B8CF0048B576; 327 | productRefGroup = D5D7C6BD1958B8CF0048B576 /* Products */; 328 | projectDirPath = ""; 329 | projectRoot = ""; 330 | targets = ( 331 | D5D7C6BB1958B8CF0048B576 /* CoreDataKit */, 332 | D5D7C6C61958B8CF0048B576 /* CoreDataKitTests */, 333 | ); 334 | }; 335 | /* End PBXProject section */ 336 | 337 | /* Begin PBXResourcesBuildPhase section */ 338 | D5D7C6BA1958B8CF0048B576 /* Resources */ = { 339 | isa = PBXResourcesBuildPhase; 340 | buildActionMask = 2147483647; 341 | files = ( 342 | ); 343 | runOnlyForDeploymentPostprocessing = 0; 344 | }; 345 | D5D7C6C51958B8CF0048B576 /* Resources */ = { 346 | isa = PBXResourcesBuildPhase; 347 | buildActionMask = 2147483647; 348 | files = ( 349 | D532100E19F8D63D007C9DCD /* EmployeesWithNull.json in Resources */, 350 | D5FE0FBE19F28340008DC1ED /* Employees.json in Resources */, 351 | D5FE0FCC19F4EAC6008DC1ED /* EmployeesReferencedRelation.json in Resources */, 352 | D532101019F8DA4D007C9DCD /* EmployeesWithOmittedField.json in Resources */, 353 | D56E1EAA19FF7409006A280C /* EmployeesNestedEmbeddingRelation.json in Resources */, 354 | D58D29D919F83604006D3B07 /* EmployeesNestedRelation.json in Resources */, 355 | ); 356 | runOnlyForDeploymentPostprocessing = 0; 357 | }; 358 | /* End PBXResourcesBuildPhase section */ 359 | 360 | /* Begin PBXSourcesBuildPhase section */ 361 | D5D7C6B71958B8CF0048B576 /* Sources */ = { 362 | isa = PBXSourcesBuildPhase; 363 | buildActionMask = 2147483647; 364 | files = ( 365 | D5B474BA19F0181F004132F8 /* NSAttributeDescription+Importing.swift in Sources */, 366 | D566F6B219F0F277003C503F /* JsonDecode+Importing.swift in Sources */, 367 | D5C4B96719FEBB390058F0AC /* NSRelationshipDescription+Importing.swift in Sources */, 368 | D50881A51A0ABF95007CA193 /* CoreDataStack+Importing.swift in Sources */, 369 | D5253DA319F1398600E8B4AF /* NSManagedObjectContext+Importing.swift in Sources */, 370 | D595F68519EF0EA100D73790 /* CoreDataStack.swift in Sources */, 371 | D5253DA119F12F1C00E8B4AF /* NSManagedObject+Importing.swift in Sources */, 372 | D5D8E81C1A0363A300991FEA /* ManagedObjectObserver.swift in Sources */, 373 | D5D7C6D91958B9300048B576 /* CDK.swift in Sources */, 374 | D55152D1196A739500857453 /* NSPersistentStore.swift in Sources */, 375 | D5BD629F1A01587800098343 /* TableViewFetchedResultsControllerDelegate.swift in Sources */, 376 | D540D82C19FE2A5E00769B84 /* Types+Importing.swift in Sources */, 377 | D595F68719EF0ED800D73790 /* Types.swift in Sources */, 378 | D5B474B819F012F9004132F8 /* NSEntityDescription+Importing.swift in Sources */, 379 | D566F6AE19F0DCA0003C503F /* NSPropertyDescription+Importing.swift in Sources */, 380 | D5253DA519F1491E00E8B4AF /* NamedManagedObject.swift in Sources */, 381 | D51AFE161965266000287A2D /* NSPersistentStoreCoordinator.swift in Sources */, 382 | D553FD971959505800E71FCE /* NSManagedObjectContext.swift in Sources */, 383 | ); 384 | runOnlyForDeploymentPostprocessing = 0; 385 | }; 386 | D5D7C6C31958B8CF0048B576 /* Sources */ = { 387 | isa = PBXSourcesBuildPhase; 388 | buildActionMask = 2147483647; 389 | files = ( 390 | D5093C6A19EF9B5700139262 /* NSPersistentStoreCoordinatorTests.swift in Sources */, 391 | D5FE0FC819F4E0D4008DC1ED /* EmployeeWithRelations.swift in Sources */, 392 | D5D7C6CF1958B8CF0048B576 /* CoreDataKitTests.swift in Sources */, 393 | D5FE0FC619F4E0D3008DC1ED /* Car.swift in Sources */, 394 | D5098CDD1A052D9F000CC246 /* ManagedObjectObserverTests.swift in Sources */, 395 | D556DF8D19EF8C71008556A8 /* CoreDataStackTests.swift in Sources */, 396 | D5FE0FC419F29FFD008DC1ED /* EmployeeImportable.swift in Sources */, 397 | D5253DA719F1591000E8B4AF /* EmployeeIncorrectEntityName.swift in Sources */, 398 | D595F68319EEE23000D73790 /* TestCase.swift in Sources */, 399 | D5EDACE319EF950B00FAC712 /* NSPersistentStoreTests.swift in Sources */, 400 | D595F67F19EEDE6000D73790 /* Employee.swift in Sources */, 401 | D5C6C63F19EEC6D60035BFFF /* NSManagedObjectContextTests.swift in Sources */, 402 | D595F68119EEE17B00D73790 /* Model.xcdatamodeld in Sources */, 403 | D56E1EAC19FF749B006A280C /* EmployeeWithRelationEmbedding.swift in Sources */, 404 | D56E1EAE19FF749B006A280C /* Salary.swift in Sources */, 405 | D5FE0FC019F286DF008DC1ED /* NSManagedObject+ImportingTests.swift in Sources */, 406 | D50881A71A0AC2CD007CA193 /* CoreDataStack+ImportingTests.swift in Sources */, 407 | ); 408 | runOnlyForDeploymentPostprocessing = 0; 409 | }; 410 | /* End PBXSourcesBuildPhase section */ 411 | 412 | /* Begin PBXTargetDependency section */ 413 | D5D7C6CA1958B8CF0048B576 /* PBXTargetDependency */ = { 414 | isa = PBXTargetDependency; 415 | target = D5D7C6BB1958B8CF0048B576 /* CoreDataKit */; 416 | targetProxy = D5D7C6C91958B8CF0048B576 /* PBXContainerItemProxy */; 417 | }; 418 | /* End PBXTargetDependency section */ 419 | 420 | /* Begin XCBuildConfiguration section */ 421 | D5D7C6D01958B8CF0048B576 /* Debug */ = { 422 | isa = XCBuildConfiguration; 423 | buildSettings = { 424 | ALWAYS_SEARCH_USER_PATHS = NO; 425 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 426 | CLANG_CXX_LIBRARY = "libc++"; 427 | CLANG_ENABLE_MODULES = YES; 428 | CLANG_ENABLE_OBJC_ARC = YES; 429 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 430 | CLANG_WARN_BOOL_CONVERSION = YES; 431 | CLANG_WARN_COMMA = YES; 432 | CLANG_WARN_CONSTANT_CONVERSION = YES; 433 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 434 | CLANG_WARN_EMPTY_BODY = YES; 435 | CLANG_WARN_ENUM_CONVERSION = YES; 436 | CLANG_WARN_INFINITE_RECURSION = YES; 437 | CLANG_WARN_INT_CONVERSION = YES; 438 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 439 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 440 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 441 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 442 | CLANG_WARN_STRICT_PROTOTYPES = YES; 443 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 444 | CLANG_WARN_UNREACHABLE_CODE = YES; 445 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 446 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 447 | COPY_PHASE_STRIP = NO; 448 | CURRENT_PROJECT_VERSION = 1; 449 | ENABLE_STRICT_OBJC_MSGSEND = YES; 450 | ENABLE_TESTABILITY = YES; 451 | GCC_C_LANGUAGE_STANDARD = gnu99; 452 | GCC_DYNAMIC_NO_PIC = NO; 453 | GCC_NO_COMMON_BLOCKS = YES; 454 | GCC_OPTIMIZATION_LEVEL = 0; 455 | GCC_PREPROCESSOR_DEFINITIONS = ( 456 | "DEBUG=1", 457 | "$(inherited)", 458 | ); 459 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 460 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 461 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 462 | GCC_WARN_UNDECLARED_SELECTOR = YES; 463 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 464 | GCC_WARN_UNUSED_FUNCTION = YES; 465 | GCC_WARN_UNUSED_VARIABLE = YES; 466 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 467 | METAL_ENABLE_DEBUG_INFO = YES; 468 | ONLY_ACTIVE_ARCH = YES; 469 | SDKROOT = iphoneos; 470 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 471 | TARGETED_DEVICE_FAMILY = "1,2"; 472 | VERSIONING_SYSTEM = "apple-generic"; 473 | VERSION_INFO_PREFIX = ""; 474 | }; 475 | name = Debug; 476 | }; 477 | D5D7C6D11958B8CF0048B576 /* Release */ = { 478 | isa = XCBuildConfiguration; 479 | buildSettings = { 480 | ALWAYS_SEARCH_USER_PATHS = NO; 481 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 482 | CLANG_CXX_LIBRARY = "libc++"; 483 | CLANG_ENABLE_MODULES = YES; 484 | CLANG_ENABLE_OBJC_ARC = YES; 485 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 486 | CLANG_WARN_BOOL_CONVERSION = YES; 487 | CLANG_WARN_COMMA = YES; 488 | CLANG_WARN_CONSTANT_CONVERSION = YES; 489 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 490 | CLANG_WARN_EMPTY_BODY = YES; 491 | CLANG_WARN_ENUM_CONVERSION = YES; 492 | CLANG_WARN_INFINITE_RECURSION = YES; 493 | CLANG_WARN_INT_CONVERSION = YES; 494 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 495 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 496 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 497 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 498 | CLANG_WARN_STRICT_PROTOTYPES = YES; 499 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 500 | CLANG_WARN_UNREACHABLE_CODE = YES; 501 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 502 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 503 | COPY_PHASE_STRIP = YES; 504 | CURRENT_PROJECT_VERSION = 1; 505 | ENABLE_NS_ASSERTIONS = NO; 506 | ENABLE_STRICT_OBJC_MSGSEND = YES; 507 | GCC_C_LANGUAGE_STANDARD = gnu99; 508 | GCC_NO_COMMON_BLOCKS = YES; 509 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 510 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 511 | GCC_WARN_UNDECLARED_SELECTOR = YES; 512 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 513 | GCC_WARN_UNUSED_FUNCTION = YES; 514 | GCC_WARN_UNUSED_VARIABLE = YES; 515 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 516 | METAL_ENABLE_DEBUG_INFO = NO; 517 | SDKROOT = iphoneos; 518 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 519 | TARGETED_DEVICE_FAMILY = "1,2"; 520 | VALIDATE_PRODUCT = YES; 521 | VERSIONING_SYSTEM = "apple-generic"; 522 | VERSION_INFO_PREFIX = ""; 523 | }; 524 | name = Release; 525 | }; 526 | D5D7C6D31958B8CF0048B576 /* Debug */ = { 527 | isa = XCBuildConfiguration; 528 | buildSettings = { 529 | CLANG_ENABLE_MODULES = YES; 530 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 531 | DEFINES_MODULE = YES; 532 | DYLIB_COMPATIBILITY_VERSION = 1; 533 | DYLIB_CURRENT_VERSION = 1; 534 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 535 | INFOPLIST_FILE = CoreDataKit/Info.plist; 536 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 537 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 538 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 539 | PRODUCT_BUNDLE_IDENTIFIER = "nl.mathijskadijk.${PRODUCT_NAME:rfc1034identifier}"; 540 | PRODUCT_NAME = "$(TARGET_NAME)"; 541 | SKIP_INSTALL = YES; 542 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 543 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 544 | SWIFT_VERSION = 4.0; 545 | }; 546 | name = Debug; 547 | }; 548 | D5D7C6D41958B8CF0048B576 /* Release */ = { 549 | isa = XCBuildConfiguration; 550 | buildSettings = { 551 | CLANG_ENABLE_MODULES = YES; 552 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 553 | DEFINES_MODULE = YES; 554 | DYLIB_COMPATIBILITY_VERSION = 1; 555 | DYLIB_CURRENT_VERSION = 1; 556 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 557 | INFOPLIST_FILE = CoreDataKit/Info.plist; 558 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 559 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 560 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 561 | PRODUCT_BUNDLE_IDENTIFIER = "nl.mathijskadijk.${PRODUCT_NAME:rfc1034identifier}"; 562 | PRODUCT_NAME = "$(TARGET_NAME)"; 563 | SKIP_INSTALL = YES; 564 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 565 | SWIFT_VERSION = 4.0; 566 | }; 567 | name = Release; 568 | }; 569 | D5D7C6D61958B8CF0048B576 /* Debug */ = { 570 | isa = XCBuildConfiguration; 571 | buildSettings = { 572 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 573 | DEVELOPMENT_TEAM = LC3Y92HQQ3; 574 | FRAMEWORK_SEARCH_PATHS = ( 575 | "$(SDKROOT)/Developer/Library/Frameworks", 576 | "$(inherited)", 577 | ); 578 | GCC_PREPROCESSOR_DEFINITIONS = ( 579 | "DEBUG=1", 580 | "$(inherited)", 581 | ); 582 | INFOPLIST_FILE = CoreDataKitTests/Info.plist; 583 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 584 | METAL_ENABLE_DEBUG_INFO = YES; 585 | PRODUCT_BUNDLE_IDENTIFIER = "nl.mathijskadijk.${PRODUCT_NAME:rfc1034identifier}"; 586 | PRODUCT_NAME = "$(TARGET_NAME)"; 587 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 588 | SWIFT_VERSION = 4.0; 589 | }; 590 | name = Debug; 591 | }; 592 | D5D7C6D71958B8CF0048B576 /* Release */ = { 593 | isa = XCBuildConfiguration; 594 | buildSettings = { 595 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 596 | DEVELOPMENT_TEAM = LC3Y92HQQ3; 597 | FRAMEWORK_SEARCH_PATHS = ( 598 | "$(SDKROOT)/Developer/Library/Frameworks", 599 | "$(inherited)", 600 | ); 601 | INFOPLIST_FILE = CoreDataKitTests/Info.plist; 602 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 603 | METAL_ENABLE_DEBUG_INFO = NO; 604 | PRODUCT_BUNDLE_IDENTIFIER = "nl.mathijskadijk.${PRODUCT_NAME:rfc1034identifier}"; 605 | PRODUCT_NAME = "$(TARGET_NAME)"; 606 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 607 | SWIFT_VERSION = 4.0; 608 | }; 609 | name = Release; 610 | }; 611 | /* End XCBuildConfiguration section */ 612 | 613 | /* Begin XCConfigurationList section */ 614 | D5D7C6B61958B8CF0048B576 /* Build configuration list for PBXProject "CoreDataKit" */ = { 615 | isa = XCConfigurationList; 616 | buildConfigurations = ( 617 | D5D7C6D01958B8CF0048B576 /* Debug */, 618 | D5D7C6D11958B8CF0048B576 /* Release */, 619 | ); 620 | defaultConfigurationIsVisible = 0; 621 | defaultConfigurationName = Release; 622 | }; 623 | D5D7C6D21958B8CF0048B576 /* Build configuration list for PBXNativeTarget "CoreDataKit" */ = { 624 | isa = XCConfigurationList; 625 | buildConfigurations = ( 626 | D5D7C6D31958B8CF0048B576 /* Debug */, 627 | D5D7C6D41958B8CF0048B576 /* Release */, 628 | ); 629 | defaultConfigurationIsVisible = 0; 630 | defaultConfigurationName = Release; 631 | }; 632 | D5D7C6D51958B8CF0048B576 /* Build configuration list for PBXNativeTarget "CoreDataKitTests" */ = { 633 | isa = XCConfigurationList; 634 | buildConfigurations = ( 635 | D5D7C6D61958B8CF0048B576 /* Debug */, 636 | D5D7C6D71958B8CF0048B576 /* Release */, 637 | ); 638 | defaultConfigurationIsVisible = 0; 639 | defaultConfigurationName = Release; 640 | }; 641 | /* End XCConfigurationList section */ 642 | 643 | /* Begin XCVersionGroup section */ 644 | D595F67B19EEDE3100D73790 /* Model.xcdatamodeld */ = { 645 | isa = XCVersionGroup; 646 | children = ( 647 | D595F67C19EEDE3100D73790 /* Model.xcdatamodel */, 648 | ); 649 | currentVersion = D595F67C19EEDE3100D73790 /* Model.xcdatamodel */; 650 | path = Model.xcdatamodeld; 651 | sourceTree = ""; 652 | versionGroupType = wrapper.xcdatamodel; 653 | }; 654 | /* End XCVersionGroup section */ 655 | }; 656 | rootObject = D5D7C6B31958B8CF0048B576 /* Project object */; 657 | } 658 | -------------------------------------------------------------------------------- /CoreDataKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CoreDataKit.xcodeproj/xcshareddata/xcbaselines/D5D7C6C61958B8CF0048B576.xcbaseline/02871957-D92B-473A-9736-E9785F6161D8.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | CoreDataKitTests 8 | 9 | testPerformanceExample() 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 0.0 15 | baselineIntegrationDisplayName 16 | Local Baseline 17 | maxPercentRelativeStandardDeviation 18 | 10 19 | 20 | 21 | 22 | 23 | performanceMetricIdentifiers 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CoreDataKit.xcodeproj/xcshareddata/xcbaselines/D5D7C6C61958B8CF0048B576.xcbaseline/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runDestinationsByUUID 6 | 7 | 02871957-D92B-473A-9736-E9785F6161D8 8 | 9 | localComputer 10 | 11 | busSpeedInMHz 12 | 100 13 | cpuCount 14 | 1 15 | cpuKind 16 | Intel Core i7 17 | cpuSpeedInMHz 18 | 2800 19 | logicalCPUCoresPerPackage 20 | 4 21 | modelCode 22 | MacBookPro11,1 23 | operatingSystemVersion 24 | 10.9.3 25 | physicalCPUCoresPerPackage 26 | 2 27 | platformIdentifier 28 | com.apple.platform.macosx 29 | 30 | targetArchitecture 31 | i386 32 | targetDevice 33 | 34 | modelCode 35 | iPhone5,1 36 | operatingSystemVersion 37 | 8.0 38 | platformIdentifier 39 | com.apple.platform.iphonesimulator 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /CoreDataKit.xcodeproj/xcshareddata/xcschemes/CoreDataKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 80 | 81 | 87 | 88 | 89 | 90 | 91 | 92 | 98 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /CoreDataKit/CDK.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CDK.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 23-06-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | public enum CDKLogLevel { 12 | case debug 13 | case info 14 | case warn 15 | case error 16 | } 17 | 18 | public typealias Logger = (CDKLogLevel, String) -> Void 19 | 20 | /** 21 | `CDK` helps with setup of the CoreData stack 22 | */ 23 | public class CDK : NSObject 24 | { 25 | fileprivate struct Holder { 26 | static var sharedStack: CoreDataStack? 27 | static var sharedLogger: Logger = { _, message in print("[CoreDataKit] \(message)") } 28 | } 29 | 30 | /** 31 | Property to hold a shared instance of CoreDataStack, all the convenience class properties access and unwrap this shared instance. So make sure to set the shared instance before doing anything else. 32 | 33 | :discussion: This is the only property you have to set to setup CoreDataKit, changing the shared instace is not supported. 34 | */ 35 | public class var sharedStack: CoreDataStack? { 36 | get { 37 | return Holder.sharedStack 38 | } 39 | 40 | set { 41 | Holder.sharedStack = newValue 42 | } 43 | } 44 | 45 | /** 46 | Shared logger used by CoreDataKit to log messages. 47 | 48 | :discussion: Default logger prints messages to console, but you can use this to use your own logger 49 | */ 50 | public class var sharedLogger: Logger { 51 | get { 52 | return Holder.sharedLogger 53 | } 54 | 55 | set { 56 | Holder.sharedLogger = newValue 57 | } 58 | } 59 | 60 | // MARK: Convenience properties 61 | 62 | /// Persistent store coordinator used as backing for the contexts of the shared stack 63 | public class var persistentStoreCoordinator: NSPersistentStoreCoordinator { 64 | return sharedStack!.persistentStoreCoordinator 65 | } 66 | 67 | /// Child context of `rootContext` with concurrency type `PrivateQueueConcurrencyType`; Perform all read/write actions on this context 68 | public class var backgroundContext: NSManagedObjectContext { 69 | return sharedStack!.backgroundContext 70 | } 71 | 72 | /// Context with concurrency type `NSMainQueueConcurrencyType`; Use only for read actions directly tied to the UI (e.g. NSFetchedResultsController) 73 | public class var mainThreadContext: NSManagedObjectContext { 74 | return sharedStack!.mainThreadContext 75 | } 76 | 77 | /** 78 | Performs the given block on the `backgroundContect` 79 | 80 | - parameter block: Block that performs the changes on the given context that should be saved 81 | - parameter completion: Completion block to run after changes are saved 82 | 83 | :see: NSManagedObjectContext.performBlock() 84 | */ 85 | public class func performOnBackgroundContext(block: @escaping PerformBlock, completionHandler: PerformBlockCompletionHandler?) { 86 | sharedStack!.performOnBackgroundContext(block: block, completionHandler: completionHandler) 87 | } 88 | 89 | @available(*, unavailable, renamed: "performOnBackgroundContext(block:completionHandler:)") 90 | public class func performBlockOnBackgroundContext(_ block: PerformBlock, completionHandler: PerformBlockCompletionHandler?) { 91 | fatalError() 92 | } 93 | 94 | public class func performOnBackgroundContext(block: @escaping PerformBlock) { 95 | sharedStack!.performOnBackgroundContext(block: block, completionHandler: nil) 96 | } 97 | 98 | @available(*, unavailable, renamed: "performOnBackgroundContext(block:)") 99 | public class func performBlockOnBackgroundContext(_ block: PerformBlock) { 100 | fatalError() 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /CoreDataKit/CDKDebugger.m: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataKitDebugger.m 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 17-05-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | #import "CDKDebugger.h" 10 | #import "CDKTypes.h" 11 | 12 | #ifdef DEBUG 13 | #define CDKBreak(msg, ...) { NSLog(msg, ##__VA_ARGS__); kill(getpid(), SIGINT); } 14 | #else 15 | #define CDKBreak(msg, ...) {} 16 | #endif 17 | 18 | static NSString *NSStringFromCDKDebuggerLogLevel(CDKDebuggerLogLevel logLevel) 19 | { 20 | NSString *string = @"Unknown"; 21 | 22 | switch (logLevel) { 23 | case CDKDebuggerLogSilent: 24 | string = @"Silent"; 25 | break; 26 | 27 | case CDKDebuggerLogVerbose: 28 | string = @"Verbose"; 29 | break; 30 | 31 | case CDKDebuggerLogInfo: 32 | string = @"Info"; 33 | break; 34 | 35 | case CDKDebuggerLogWarning: 36 | string = @"Warning"; 37 | break; 38 | 39 | case CDKDebuggerLogError: 40 | string = @"Error"; 41 | break; 42 | } 43 | 44 | return string; 45 | } 46 | 47 | @implementation CDKDebugger 48 | 49 | + (instancetype)sharedDebugger 50 | { 51 | static CDKDebugger *sharedDebugger; 52 | static dispatch_once_t onceToken; 53 | dispatch_once(&onceToken, ^{ 54 | sharedDebugger = [[CDKDebugger alloc] init]; 55 | }); 56 | 57 | return sharedDebugger; 58 | } 59 | 60 | - (instancetype)init 61 | { 62 | self = [super init]; 63 | if (!self) { 64 | return nil; 65 | } 66 | 67 | #ifdef DEBUG 68 | self.logLevel = CDKDebuggerLogWarning; 69 | self.breakOnLogLevel = CDKDebuggerLogError; 70 | #else 71 | self.logLevel = CDKDebuggerLogSilent; 72 | self.breakOnLogLevel = CDKDebuggerLogSilent; 73 | #endif 74 | 75 | return self; 76 | } 77 | 78 | - (CDKDebuggerAction)log:(NSArray *)messages atLevel:(CDKDebuggerLogLevel)logLevel 79 | { 80 | CDKDebuggerAction actions = CDKDebuggerActionNone; 81 | 82 | // Log message if required by log level 83 | if (CDKDebuggerLogSilent != self.logLevel && logLevel >= self.logLevel) 84 | { 85 | [messages enumerateObjectsUsingBlock:^(NSString *message, NSUInteger idx, BOOL *stop) { 86 | NSLog(@"[CoreDataKit] %@ %@", NSStringFromCDKDebuggerLogLevel(logLevel).uppercaseString, message); 87 | }]; 88 | actions = actions | CDKDebuggerActionLogged; 89 | } 90 | 91 | // Break execution if required by log level 92 | if (CDKDebuggerLogSilent != self.breakOnLogLevel && logLevel >= self.breakOnLogLevel) 93 | { 94 | CDKBreak(@"[CoreDataKit] CDKDebugger will now break so you can investigate."); 95 | actions = actions | CDKDebuggerActionBreakpoint; 96 | } 97 | 98 | return actions; 99 | } 100 | 101 | - (CDKDebuggerAction)handleError:(NSError *)error 102 | { 103 | CDKDebuggerAction actions = CDKDebuggerActionNone; 104 | 105 | if (error) 106 | { 107 | #warning Should check if we this error logging is to our liking 108 | NSMutableArray *messages = @[].mutableCopy; 109 | 110 | // Log all values in the user info dict, also iterate over arrays/errors in the dict 111 | [error.userInfo.allValues enumerateObjectsUsingBlock:^(id value, NSUInteger idx, BOOL *stop) { 112 | if ([value isKindOfClass:[NSArray class]]) 113 | { 114 | [value enumerateObjectsUsingBlock:^(id valueInArray, NSUInteger idxInArray, BOOL *stopInArray) { 115 | id valueToLog = ([valueInArray respondsToSelector:@selector(userInfo)]) ? [valueInArray userInfo] : valueInArray; 116 | [messages addObject:[NSString stringWithFormat:@"Error Details: %@", valueToLog]]; 117 | }]; 118 | } 119 | else 120 | { 121 | [messages addObject:[NSString stringWithFormat:@"Error: %@", value]]; 122 | } 123 | }]; 124 | 125 | // Log error info 126 | [messages addObject:[NSString stringWithFormat:@"Error Message: %@", error.localizedDescription]]; 127 | [messages addObject:[NSString stringWithFormat:@"Error Domain: %@", error.domain]]; 128 | [messages addObject:[NSString stringWithFormat:@"Recovery Suggestion: %@", error.localizedRecoverySuggestion]]; 129 | 130 | actions = [self log:messages atLevel:CDKDebuggerLogError]; 131 | } 132 | 133 | return actions; 134 | } 135 | 136 | @end 137 | -------------------------------------------------------------------------------- /CoreDataKit/CoreDataStack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataStack.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 15-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | public class CoreDataStack: NSObject { 12 | /// Persistent store coordinator used as backing for the contexts 13 | public let persistentStoreCoordinator: NSPersistentStoreCoordinator 14 | 15 | /// Root context that is directly associated with the `persistentStoreCoordinator` and does it work on a background queue; Do not use directly 16 | public let rootContext: NSManagedObjectContext 17 | 18 | /// Context with concurrency type `NSMainQueueConcurrencyType`; Use only for read actions directly tied to the UI (e.g. NSFetchedResultsController) 19 | public let mainThreadContext: NSManagedObjectContext 20 | 21 | /// Child context of `rootContext` with concurrency type `PrivateQueueConcurrencyType`; Perform all read/write actions on this context 22 | public let backgroundContext: NSManagedObjectContext 23 | 24 | /** 25 | Create a stack based on the given `NSPersistentStoreCoordinator`. 26 | 27 | - parameter persistentStoreCoordinator: The coordinator that will be coordinate the persistent store of this stack 28 | */ 29 | public init(persistentStoreCoordinator: NSPersistentStoreCoordinator) { 30 | self.persistentStoreCoordinator = persistentStoreCoordinator 31 | 32 | self.rootContext = NSManagedObjectContext(persistentStoreCoordinator: self.persistentStoreCoordinator) 33 | self.rootContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy 34 | 35 | self.mainThreadContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType, parentContext: rootContext) 36 | 37 | self.backgroundContext = self.mainThreadContext.createChildContext() 38 | 39 | super.init() 40 | 41 | // TODO: In de huidige setup, nobody cares, want main context zit tussen de saves in en krijgt vanzelf de notificaties 42 | //NSNotificationCenter.defaultCenter().addObserver(self, selector: "rootContextDidSave:", name: NSManagedObjectContextDidSaveNotification, object: rootContext) 43 | } 44 | 45 | // deinit { 46 | // NSNotificationCenter.defaultCenter().removeObserver(self) 47 | // } 48 | 49 | // MARK: Convenience methods 50 | 51 | /** 52 | Performs the given block on the `backgroundContect` 53 | 54 | - parameter block: Block that performs the changes on the given context that should be saved 55 | - parameter completion: Completion block to run after changes are saved 56 | 57 | :see: NSManagedObjectContext.performBlock() 58 | */ 59 | public func performOnBackgroundContext(block: @escaping PerformBlock, completionHandler: PerformBlockCompletionHandler?) { 60 | backgroundContext.perform(block: block, completionHandler: completionHandler) 61 | } 62 | 63 | @available(*, unavailable, renamed: "performOnBackgroundContext(block:completionHandler:)") 64 | public func performBlockOnBackgroundContext(_ block: PerformBlock, completionHandler: PerformBlockCompletionHandler?) { 65 | fatalError() 66 | } 67 | 68 | public func performOnBackgroundContext(block: @escaping PerformBlock) { 69 | backgroundContext.perform(block: block, completionHandler: nil) 70 | } 71 | 72 | @available(*, unavailable, renamed: "performOnBackgroundContext(block:)") 73 | public func performBlockOnBackgroundContext(_ block: PerformBlock) { 74 | fatalError() 75 | } 76 | 77 | /** 78 | Dumps some debug info about this stack to the console 79 | */ 80 | public func dumpStack() { 81 | CDK.sharedLogger(.debug, "Stores: \(persistentStoreCoordinator.persistentStores)") 82 | CDK.sharedLogger(.debug, " - Store coordinator: \(persistentStoreCoordinator.debugDescription)") 83 | CDK.sharedLogger(.debug, " |- Root context: \(rootContext.debugDescription)") 84 | CDK.sharedLogger(.debug, " |- Main thread context: \(mainThreadContext.debugDescription)") 85 | CDK.sharedLogger(.debug, " |- Background context: \(backgroundContext.debugDescription)") 86 | } 87 | 88 | // MARK: Notification observers 89 | 90 | // func rootContextDidSave(notification: NSNotification) { 91 | // if NSThread.isMainThread() { 92 | // if let updatedObjects = notification.userInfo?[NSUpdatedObjectsKey] as? NSSet { 93 | // for _object in updatedObjects { 94 | // let object = _object as! NSManagedObject 95 | // mainThreadContext.objectWithID(object.objectID).willAccessValueForKey(nil) 96 | // } 97 | // } 98 | // 99 | // mainThreadContext.mergeChangesFromContextDidSaveNotification(notification) 100 | // } else { 101 | // dispatch_async(dispatch_get_main_queue()) { 102 | // self.rootContextDidSave(notification) 103 | // } 104 | // } 105 | // } 106 | } 107 | -------------------------------------------------------------------------------- /CoreDataKit/Importing/CoreDataStack+Importing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataStack+Importing.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 05-11-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | extension CoreDataStack { 12 | /** 13 | Dumps the current import configuration to the logger 14 | */ 15 | public func dumpImportConfiguration() { 16 | let entityUserInfoKeys = [IdentifierUserInfoKey] 17 | 18 | for (entityName, entity) in persistentStoreCoordinator.managedObjectModel.entitiesByName { 19 | CDK.sharedLogger(.debug, " ") 20 | CDK.sharedLogger(.debug, "\(entityName):") 21 | 22 | for (_key, value) in entity.userInfo! { 23 | let key = _key as! String 24 | 25 | if !entityUserInfoKeys.contains(key) { 26 | CDK.sharedLogger(.debug, " ⚠ \(key) → \(value)") 27 | } 28 | } 29 | 30 | do { 31 | let identifyingAttibute = try entity.identifyingAttribute() 32 | dumpPropertyDescription(identifyingAttibute, asIdentifyingAttribute: true) 33 | } 34 | catch { 35 | } 36 | 37 | for (_, attribute) in entity.attributesByName { 38 | dumpPropertyDescription(attribute) 39 | } 40 | 41 | for (_, relationship) in entity.relationshipsByName { 42 | dumpPropertyDescription(relationship) 43 | } 44 | } 45 | } 46 | 47 | fileprivate func dumpPropertyDescription(_ property: NSPropertyDescription, asIdentifyingAttribute: Bool = false) { 48 | var propertyUserInfoKeys = [MappingUserInfoKey] 49 | for i in 0...MaxNumberedMappings+1 { 50 | propertyUserInfoKeys.append(MappingUserInfoKey + ".\(i)") 51 | } 52 | 53 | let attributeUserInfoKeys: [String] = [] 54 | let relationshipUserInfoKeys = [RelationTypeUserInfoKey] 55 | 56 | let identifying = asIdentifyingAttribute ? "★" : " " 57 | let indexed = property.isIndexed ? "⚡" : "" 58 | let optional = property.isOptional ? "?" : "" 59 | let relationshipType = (property as? NSRelationshipDescription)?.relationType.rawValue 60 | let relationshipTypeDescription = relationshipType == nil ? "" : " → \(relationshipType!)" 61 | 62 | CDK.sharedLogger(.debug, "\(identifying)\(indexed)\(property.name)\(optional) → \(property.mappings)\(relationshipTypeDescription)") 63 | 64 | for (_key, value) in property.userInfo! { 65 | let key = _key as! String 66 | 67 | if !propertyUserInfoKeys.contains(key) { 68 | if (property is NSAttributeDescription && !attributeUserInfoKeys.contains(key)) || 69 | (property is NSRelationshipDescription && !relationshipUserInfoKeys.contains(key)) { 70 | CDK.sharedLogger(.debug, " ⚠ \(key) → \(value)") 71 | } 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /CoreDataKit/Importing/JsonDecode+Importing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JsonDecode+Importing.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 17-10-14. 6 | // Copyright (c) 2014 Tom Lokhorst & Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // We use the decode functions from the JsonDecode+Importing.swift file 12 | extension String { 13 | static func decode(_ json : AnyObject) -> String? { 14 | return json as? String 15 | } 16 | } 17 | 18 | extension Bool { 19 | static func decode(_ json : AnyObject) -> Bool? { 20 | return json as? Bool 21 | } 22 | } 23 | 24 | extension Int { 25 | static func decode(_ json : AnyObject) -> Int? { 26 | return json as? Int 27 | } 28 | } 29 | 30 | extension Int64 { 31 | static func decode(_ json : AnyObject) -> Int64? { 32 | let number = json as? NSNumber 33 | return number.map { $0.int64Value } 34 | } 35 | } 36 | 37 | extension Double { 38 | static func decode(_ json : AnyObject) -> Double? { 39 | return json as? Double 40 | } 41 | } 42 | 43 | extension Data { 44 | static func decode(_ json: AnyObject) -> Data? { 45 | return json as? Data 46 | } 47 | } 48 | 49 | extension Date { 50 | struct DateFormatter { 51 | static let withTimeZone : Foundation.DateFormatter = { 52 | let formatter = Foundation.DateFormatter() 53 | formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" 54 | formatter.locale = Locale(identifier: "en_US_POSIX") 55 | 56 | return formatter 57 | }() 58 | } 59 | 60 | static func decode(_ json : AnyObject) -> Date? { 61 | if let date = json as? Date { 62 | return date 63 | } else if let dateString = json as? String { 64 | return DateFormatter.withTimeZone.date(from: dateString) 65 | } 66 | 67 | return nil 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /CoreDataKit/Importing/NSAttributeDescription+Importing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributeDescription+Importing.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 16-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | extension NSAttributeDescription 12 | { 13 | /** 14 | Transform given value to a value that can be saved into this attribute in CoreData 15 | 16 | - parameter value: The value to transform 17 | 18 | - returns: The transformed value or nil if the value can't for into this attribute 19 | */ 20 | func transform(value: AnyObject) -> AnyObject? { 21 | switch attributeType { 22 | case .integer16AttributeType: 23 | fallthrough 24 | case .integer32AttributeType: 25 | return Int.decode(value) as AnyObject? 26 | 27 | case .integer64AttributeType: 28 | if let int64 = Int64.decode(value) { 29 | return NSNumber(value: int64 as Int64) 30 | } else { 31 | return nil 32 | } 33 | 34 | case .decimalAttributeType: 35 | fallthrough 36 | case .doubleAttributeType: 37 | fallthrough 38 | case .floatAttributeType: 39 | return Double.decode(value) as AnyObject? 40 | 41 | case .stringAttributeType: 42 | return String.decode(value) as AnyObject? 43 | 44 | case .booleanAttributeType: 45 | return Bool.decode(value) as AnyObject? 46 | 47 | case .dateAttributeType: 48 | return Date.decode(value) as AnyObject? 49 | 50 | case .binaryDataAttributeType: 51 | return Data.decode(value) as AnyObject? 52 | 53 | case .undefinedAttributeType: 54 | fallthrough 55 | case .transformableAttributeType: 56 | fallthrough 57 | case .UUIDAttributeType: 58 | fallthrough 59 | case .URIAttributeType: 60 | fallthrough 61 | case .objectIDAttributeType: 62 | return nil 63 | } 64 | } 65 | 66 | @available(*, unavailable, renamed: "transform(value:)") 67 | func transformValue(_ value: AnyObject) -> AnyObject? { 68 | fatalError() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /CoreDataKit/Importing/NSEntityDescription+Importing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSEntityDescription+Importing.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 16-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | extension NSEntityDescription 12 | { 13 | /** 14 | Get the attribute description of this entity description that is marked as identifier in the model 15 | 16 | - returns: Result with the identifying attribute description 17 | */ 18 | func identifyingAttribute() throws -> NSAttributeDescription { 19 | if let identifyingAttributeName = userInfo?[IdentifierUserInfoKey] as? String { 20 | if let identifyingAttribute = self.attributesByName[identifyingAttributeName] { 21 | return identifyingAttribute 22 | } 23 | 24 | let error = CoreDataKitError.importError(description: "Found \(IdentifierUserInfoKey) with value '\(identifyingAttributeName)' but that isn't a valid attribute name") 25 | throw error 26 | } else if let superEntity = self.superentity { 27 | return try superEntity.identifyingAttribute() 28 | } 29 | 30 | let error = CoreDataKitError.importError(description: "No \(IdentifierUserInfoKey) value found on \(name)") 31 | throw error 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /CoreDataKit/Importing/NSManagedObject+Importing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObject+Importing.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 14-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | extension NSManagedObject 12 | { 13 | /** 14 | Import a dictionary into this managed object 15 | 16 | - parameter dictionary: The dictionary to import 17 | 18 | - returns: Result wheter the import was performed 19 | */ 20 | public func importDictionary(_ dictionary: [String: AnyObject]) throws { 21 | if shouldImport(dictionary: dictionary) { 22 | let transformedDictionary = willImport(dictionary: dictionary) 23 | do { 24 | try performImport(dictionary: transformedDictionary) 25 | didImport(dictionary: transformedDictionary, error: nil) 26 | } 27 | catch let err { 28 | didImport(dictionary: transformedDictionary, error: err) 29 | throw err 30 | } 31 | 32 | return 33 | } 34 | 35 | let entityName = self.entity.name ?? "nil" 36 | let error = CoreDataKitError.importCancelled(entityName: entityName) 37 | throw error 38 | } 39 | 40 | // MARK: Steps in the import process 41 | 42 | /** 43 | Will be called before import to determine wheter this dictionary should be imported into this object or not 44 | 45 | :discusion: Default implementation just returns true 46 | 47 | - parameter dictionary: The dictionary that will be imported 48 | 49 | :return: Wheter to import or not 50 | */ 51 | open func shouldImport(dictionary: [String: AnyObject]) -> Bool { 52 | return true 53 | } 54 | 55 | @available(*, unavailable, renamed: "shouldImport(dictionary:)") 56 | public func shouldImport(_ dictionary: [String: AnyObject]) -> Bool { 57 | fatalError() 58 | } 59 | 60 | /** 61 | Will be called right before import gives the chance to change the imported dictionary 62 | 63 | :discusion: Default implementation just returns the given dictionary 64 | 65 | - parameter dictionary: The dictionary that is given to the import method 66 | 67 | :return: The dictionary that will be used in the rest of the import process 68 | */ 69 | open func willImport(dictionary: [String: AnyObject]) -> [String: AnyObject] { 70 | return dictionary 71 | } 72 | 73 | @available(*, unavailable, renamed: "willImport(dictionary:)") 74 | open func willImport(_ dictionary: [String: AnyObject]) -> [String: AnyObject] { 75 | fatalError() 76 | } 77 | 78 | /** 79 | Performs the import process 80 | 81 | - parameter dictionary: The dictionary to import 82 | 83 | - returns: Result wheter the import succeeded 84 | */ 85 | fileprivate func performImport(dictionary: [String : AnyObject]) throws { 86 | if let context = managedObjectContext { 87 | for propertyDescription in entity.properties { 88 | 89 | switch propertyDescription { 90 | case let attributeDescription as NSAttributeDescription: 91 | try performImportAttribute(attributeDescription, dictionary: dictionary) 92 | 93 | case let relationshipDescription as NSRelationshipDescription: 94 | try performImportRelationship(context, relationship: relationshipDescription, dictionary: dictionary) 95 | 96 | case is NSFetchedPropertyDescription: 97 | let error = CoreDataKitError.importError(description: "Importing NSFetchedPropertyDescription is not supported") 98 | throw error 99 | 100 | default: 101 | let error = CoreDataKitError.importError(description: "Importing unknown subclass or no subclass of NSPropertyDescription is not supported") 102 | throw error 103 | } 104 | } 105 | } else { 106 | let error = CoreDataKitError.importError(description: "Managed object not inserted in context, objects must be inserted before importing") 107 | throw error 108 | } 109 | } 110 | 111 | /** 112 | Called after import is performed 113 | 114 | - parameter dictionary: The dictionary that was imported, this is the dictionary returned by willImport 115 | - parameter error: Optional error if import failed 116 | */ 117 | open func didImport(dictionary: [String : AnyObject], error: Error?) { 118 | // No-op 119 | } 120 | 121 | @available(*, unavailable, renamed: "didImport(dictionary:)") 122 | open func didImport(_ dictionary: [String : AnyObject], error: Error?) { 123 | fatalError() 124 | } 125 | 126 | // MARK: Import helpers 127 | 128 | /** 129 | Performs the import of one attribute 130 | 131 | - parameter attribute: The attribute to perform the import on 132 | - parameter dictionary: The dictionary to import from 133 | 134 | - returns: Result wheter import succeeded 135 | */ 136 | fileprivate func performImportAttribute(_ attribute: NSAttributeDescription, dictionary: [String: AnyObject]) throws { 137 | switch attribute.preferredValueFromDictionary(dictionary) { 138 | case let .some(value): 139 | if let transformedValue: AnyObject = attribute.transform(value: value) { 140 | setValue(transformedValue, forKeyPath: attribute.name) 141 | } else { 142 | let error = CoreDataKitError.importError(description: "Value '\(value)' could not be transformed to a value compatible with the type of \(entity.name).\(attribute.name)") 143 | throw error 144 | } 145 | 146 | case .null: 147 | setValue(nil, forKeyPath: attribute.name) // We just set it to nil, maybe there is a default value in the model 148 | 149 | case .none: 150 | // Not found in dictionary, do not change value 151 | break; 152 | } 153 | } 154 | 155 | /** 156 | Performs the import of one attribute 157 | 158 | - parameter relationship: The relationship to perform the import on 159 | - parameter dictionary: The dictionary to import from 160 | 161 | - returns: Result wheter import succeeded 162 | */ 163 | fileprivate func performImportRelationship(_ context: NSManagedObjectContext, relationship: NSRelationshipDescription, dictionary: [String : AnyObject]) throws { 164 | if let destinationEntity = relationship.destinationEntity { 165 | let importableValue = relationship.preferredValueFromDictionary(dictionary) 166 | 167 | switch relationship.relationType { 168 | case .Reference: 169 | try performImportReferenceRelationship(context, relationship: relationship, importableValue: importableValue, destinationEntity: destinationEntity) 170 | 171 | case .Embedding: 172 | try performImportEmbeddingRelationship(context, relationship: relationship, importableValue: importableValue, destinationEntity: destinationEntity) 173 | } 174 | } else { 175 | let error = CoreDataKitError.importError(description: "Relationship \(self.entity.name).\(relationship.name) has no destination entity defined") 176 | throw error 177 | } 178 | } 179 | 180 | fileprivate func performImportReferenceRelationship(_ context: NSManagedObjectContext, relationship: NSRelationshipDescription, importableValue: ImportableValue, destinationEntity: NSEntityDescription) throws { 181 | switch importableValue { 182 | case let .some(value as [String: AnyObject]): 183 | let object = try context.importEntity(destinationEntity, dictionary: value) 184 | try self.updateRelationship(context, relationship: relationship, withValue: object, deleteCurrent: false) 185 | 186 | case .some(_ as [AnyObject]): 187 | let error = CoreDataKitError.unimplementedMethod(description: "Multiple referenced / nested relationships not yet supported with relation type \(RelationType.Reference)") 188 | throw error 189 | 190 | case let .some(value): 191 | let object = try context.findEntityByIdentifyingAttribute(destinationEntity, identifyingValue: value) 192 | try self.updateRelationship(context, relationship: relationship, withValue: object, deleteCurrent: false) 193 | 194 | case .null: 195 | return try updateRelationship(context, relationship: relationship, withValue: nil, deleteCurrent: false) 196 | 197 | case .none: 198 | return // Not found in dictionary, do not change value 199 | } 200 | } 201 | 202 | fileprivate func performImportEmbeddingRelationship(_ context: NSManagedObjectContext, relationship: NSRelationshipDescription, importableValue: ImportableValue, destinationEntity: NSEntityDescription) throws { 203 | switch importableValue { 204 | case let .some(value as [String: AnyObject]): 205 | let destinationObject = try context.create(destinationEntity) 206 | try destinationObject.importDictionary(value) 207 | try self.updateRelationship(context, relationship: relationship, withValue: destinationObject, deleteCurrent: true) 208 | 209 | case .some(_ as [AnyObject]): 210 | let error = CoreDataKitError.unimplementedMethod(description: "Multiple nested relationships not yet supported with relation type \(RelationType.Embedding)") 211 | throw error 212 | 213 | case .some(_): 214 | let error = CoreDataKitError.unimplementedMethod(description: "Referenced relationships are not supported with relation type \(RelationType.Embedding)") 215 | throw error 216 | 217 | case .null: 218 | try self.updateRelationship(context, relationship: relationship, withValue: nil, deleteCurrent: true) 219 | 220 | case .none: 221 | return // Not found in dictionary, do not change value 222 | } 223 | } 224 | 225 | /** 226 | Helper to update relationship value, adds or sets the relation to the given value or on nil value clears/deletes the whole relation 227 | 228 | - parameter value: The value to update the relationship with 229 | - parameter relationship: The relationship to update 230 | 231 | :return: Wheter the update succeeded 232 | */ 233 | fileprivate func updateRelationship(_ context: NSManagedObjectContext, relationship: NSRelationshipDescription, withValue _value: NSManagedObject?, deleteCurrent: Bool) throws { 234 | if (relationship.isToMany) { 235 | if let objectSet = value(forKeyPath: relationship.name) as? NSMutableSet { 236 | if (deleteCurrent) { 237 | for object in objectSet { 238 | if let managedObject = object as? NSManagedObject { 239 | do { 240 | try context.deleteWithPermanentID(managedObject) 241 | } 242 | catch { 243 | } 244 | } 245 | } 246 | } 247 | 248 | if let object = _value { 249 | objectSet.add(object) 250 | } else { 251 | objectSet.removeAllObjects() 252 | } 253 | } else { 254 | let error = CoreDataKitError.importError(description: "Can't append imported object to to-many relation '\(entity.name).\(relationship.name)' because it's not a NSMutableSet") 255 | throw error 256 | } 257 | } else { 258 | if (deleteCurrent) { 259 | if let currentRelatedObject = self.value(forKeyPath: relationship.name) as? NSManagedObject { 260 | do { 261 | try context.deleteWithPermanentID(currentRelatedObject) 262 | } 263 | catch { 264 | } 265 | } 266 | } 267 | 268 | if let value = _value { 269 | setValue(value, forKeyPath: relationship.name) 270 | } else if (relationship.isOptional) { 271 | setValue(nil, forKeyPath: relationship.name) 272 | } else { 273 | let error = CoreDataKitError.importError(description: "Relationship \(self.entity.name).\(relationship.name) is not optional, cannot set to null") 274 | throw error 275 | } 276 | } 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /CoreDataKit/Importing/NSManagedObjectContext+Importing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObjectContext+Importing.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 17-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | extension NSManagedObjectContext 12 | { 13 | /** 14 | Import dictionary into the given type of entity, will do lookups in the context to find the object to import 15 | 16 | :see: Importing documentation at [TODO] 17 | 18 | - parameter entity: Type of entity to import 19 | - parameter dictionary: Data to import 20 | 21 | - returns: Result with managed object of the given entity type with the data imported on it 22 | */ 23 | public func importEntity(_ entity: T.Type, dictionary: [String : AnyObject]) throws -> T where T:NamedManagedObject { 24 | let desc = try entityDescription(entity) 25 | return try self.importEntity(desc, dictionary: dictionary) 26 | } 27 | 28 | /** 29 | Import dictionary into an entity based on entity description, will do lookups in the context to find the object to import 30 | 31 | :see: Importing documentation at [TODO] 32 | 33 | - parameter entityDescription: Description of entity to import 34 | - parameter dictionary: Data to import 35 | 36 | - returns: Result with managed object of the given entity type with the data imported on it 37 | */ 38 | func importEntity(_ entityDescription: NSEntityDescription, dictionary: [String : AnyObject]) throws -> T { 39 | 40 | let identifyingAttribute = try entityDescription.identifyingAttribute() 41 | switch identifyingAttribute.preferredValueFromDictionary(dictionary) { 42 | case let .some(value): 43 | let object: T = try self.objectForImport(entityDescription, identifyingValue: value) 44 | try object.importDictionary(dictionary) 45 | return object 46 | 47 | case .null: 48 | let error = CoreDataKitError.importError(description: "Value 'null' in import dictionary for identifying atribute '\(entityDescription.name).\(identifyingAttribute.name)', dictionary: \(dictionary)") 49 | throw error 50 | 51 | case .none: 52 | let error = CoreDataKitError.importError(description: "No value in import dictionary for identifying atribute '\(entityDescription.name).\(identifyingAttribute.name)', dictionary: \(dictionary)") 53 | throw error 54 | } 55 | } 56 | 57 | /** 58 | Find or create an instance of this managed object to use for import 59 | 60 | - parameter entityDescription: Description of entity to import 61 | - parameter identifyingValue: The identifying value of the object 62 | 63 | :return: Result with the object to perform the import on 64 | */ 65 | fileprivate func objectForImport(_ entityDescription: NSEntityDescription, identifyingValue: AnyObject) throws -> T { 66 | do { 67 | if let object: T = try findEntityByIdentifyingAttribute(entityDescription, identifyingValue: identifyingValue) { 68 | return object 69 | } 70 | } 71 | catch { 72 | } 73 | 74 | return try create(entityDescription) 75 | } 76 | 77 | /** 78 | Find entity based on the identifying attribute 79 | 80 | - parameter entityDescription: Description of entity to find 81 | - parameter identifyingValue: The identifying value of the object 82 | 83 | - returns: Result with the optional object that is found, nil on not found 84 | */ 85 | func findEntityByIdentifyingAttribute(_ entityDescription: NSEntityDescription, identifyingValue: AnyObject) throws -> T? { 86 | 87 | let identifyingAttribute = try entityDescription.identifyingAttribute() 88 | let predicate = NSPredicate(format: "%K = %@", argumentArray: [identifyingAttribute.name, identifyingValue]) 89 | let objects = try self.find(entityDescription, predicate: predicate) 90 | 91 | if objects.count > 1 { 92 | let error = CoreDataKitError.importError(description: "Expected 0...1 result, got \(objects.count) results") 93 | throw error 94 | } 95 | 96 | return objects.first as? T 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /CoreDataKit/Importing/NSPropertyDescription+Importing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSPropertyDescription+Importing.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 17-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | extension NSPropertyDescription 12 | { 13 | var mapStrategy: MapStrategy { 14 | let fallbackStrategy = MapStrategy.Mapping 15 | 16 | if let mappingStrategyString = userInfo?[MapStrategyUserInfoKey] as? String { 17 | if let strategy = MapStrategy(rawValue: mappingStrategyString) { 18 | return strategy 19 | } else { 20 | CDK.sharedLogger(.error, "Unsupported \(MappingUserInfoKey) given for \(entity.name).\(name), falling back to \(fallbackStrategy.rawValue) strategy") 21 | return fallbackStrategy 22 | } 23 | } 24 | 25 | return fallbackStrategy 26 | } 27 | 28 | /** 29 | Keys that could contain data for this property as defined by the model 30 | 31 | - returns: Array of keys to look for when mapping data into this property 32 | */ 33 | var mappings: [String] { 34 | switch mapStrategy { 35 | case .NoMapping: 36 | return [String]() 37 | 38 | case .Mapping: 39 | var _mappings = [String]() 40 | 41 | // Fetch the unnumbered mapping 42 | if let unnumberedMapping = userInfo?[MappingUserInfoKey] as? String { 43 | _mappings.append(unnumberedMapping) 44 | } 45 | 46 | // Fetch the numbered mappings 47 | for i in 0...MaxNumberedMappings+1 { 48 | if let numberedMapping = userInfo?[MappingUserInfoKey + ".\(i)"] as? String { 49 | _mappings.append(numberedMapping) 50 | 51 | if i == MaxNumberedMappings+1 { 52 | CDK.sharedLogger(.warn, "Only mappings up to \(MappingUserInfoKey).\(MaxNumberedMappings) mappings are supported all others are ignored, you defined more for \(entity.name).\(name)") 53 | } 54 | } 55 | } 56 | 57 | // Fallback to the name of the property as a mapping if no mappings are defined 58 | if 0 == _mappings.count { 59 | _mappings.append(name) 60 | } 61 | 62 | return _mappings 63 | } 64 | } 65 | 66 | /** 67 | Looks at the available mappings and takes the preferred value out of the given dictionary based on those mappings 68 | 69 | - parameter dictionary: Data to import from 70 | 71 | - returns: Value to import 72 | */ 73 | func preferredValueFromDictionary(_ dictionary: [String: AnyObject]) -> ImportableValue { 74 | for keyPath in mappings { 75 | if let value: AnyObject = (dictionary as NSDictionary).value(forKeyPath: keyPath) as AnyObject? { 76 | if value is NSNull { 77 | return .null 78 | } else { 79 | return .some(value) 80 | } 81 | } 82 | } 83 | 84 | return .none 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /CoreDataKit/Importing/NSRelationshipDescription+Importing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSRelationshipDescription+Importing.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 27-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | extension NSRelationshipDescription { 12 | 13 | /// Type of the relation as defined in the model 14 | var relationType: RelationType { 15 | let fallbackRelationType = RelationType.Reference 16 | 17 | if let relationTypeString = userInfo?[RelationTypeUserInfoKey] as? String { 18 | if let relationType = RelationType(rawValue: relationTypeString) { 19 | return relationType 20 | } else { 21 | CDK.sharedLogger(.error, "Unsupported \(RelationTypeUserInfoKey) given for \(entity.name).\(name), falling back to \(fallbackRelationType.rawValue) relation type") 22 | return fallbackRelationType 23 | } 24 | } 25 | 26 | return fallbackRelationType 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CoreDataKit/Importing/Types+Importing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Types+Importing.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 27-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - User info values 12 | 13 | /// Key used on entity to define the identifying attribute in CoreData user info 14 | let IdentifierUserInfoKey = "CDKId" 15 | 16 | // MARK: Mapping 17 | 18 | /// Key used on property to define mapping in CoreData user info 19 | let MappingUserInfoKey = "CDKMap" 20 | 21 | /// Maximum of numbered MappingUserInfoKeys on an property 22 | let MaxNumberedMappings = 9 23 | 24 | /// Key used on property to define mapping strategy in CoreData user info 25 | let MapStrategyUserInfoKey = "CDKMapStrategy" 26 | 27 | /// Type of mapping to use 28 | enum MapStrategy: String { 29 | /// Stategy to use default mapping behaviour with the available MappingUserInfoKey and fallbacks 30 | case Mapping = "CDKStandardMapping" 31 | 32 | /// Strategy to disable all mapping behaviour 33 | case NoMapping = "CDKNoMapping" 34 | } 35 | 36 | // MARK: Relations 37 | 38 | /// Key used on relation to define type of the relation in CoreData user info 39 | let RelationTypeUserInfoKey = "CDKRelationType" 40 | 41 | /// Values used with RelationTypeUserInfoKey to alter relation type 42 | enum RelationType: String { 43 | /// Relation that is referenced by a primary key like ID 44 | case Reference = "CDKReference" 45 | 46 | /// Relation that doesn't use a ID of some sort 47 | case Embedding = "CDKEmbedding" 48 | } 49 | 50 | // MARK: - Importable value 51 | 52 | /// Value extracted from source that can be imported into a managed object 53 | enum ImportableValue { 54 | // Some value is found 55 | case some(AnyObject) 56 | 57 | // Value should be set to null 58 | case null 59 | 60 | // No value is found 61 | case none 62 | } 63 | -------------------------------------------------------------------------------- /CoreDataKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CoreDataKit/ManagedObjectObserver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Observable.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 31-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | public enum ObservedAction { 12 | case updated(T) 13 | case refreshed(T) 14 | case inserted(T) 15 | case deleted 16 | 17 | public func value() -> T? { 18 | switch self { 19 | case let .updated(val): 20 | return val 21 | case let .refreshed(val): 22 | return val 23 | case let .inserted(val): 24 | return val 25 | 26 | case .deleted: 27 | return nil 28 | } 29 | } 30 | } 31 | 32 | public class ManagedObjectObserver: NSObject { 33 | public typealias Subscriber = (ObservedAction) -> Void 34 | 35 | public let observedObject: T 36 | let context: NSManagedObjectContext 37 | var notificationObserver: NSObjectProtocol? 38 | var subscribers: [Subscriber] 39 | 40 | /** 41 | Start observing changes on a `NSManagedObject` in a certain context. 42 | 43 | - parameter observeObject: Object to observe 44 | - parameter inContext: Context to observe the object in 45 | */ 46 | public init(observeObject originalObserveObject: T, inContext context: NSManagedObjectContext) { 47 | // Try to convert the observee to the given context, may fail because it's not yet saved 48 | let observeObject = try? context.find(T.self, managedObjectID: originalObserveObject.objectID) 49 | self.observedObject = observeObject ?? originalObserveObject 50 | 51 | self.context = context 52 | self.subscribers = [Subscriber]() 53 | super.init() 54 | 55 | notificationObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.NSManagedObjectContextObjectsDidChange, object: context, queue: nil) { [weak self] notification in 56 | guard let strongSelf = self else { 57 | return 58 | } 59 | 60 | context.perform { 61 | if strongSelf.subscribers.isEmpty { 62 | return 63 | } 64 | 65 | do { 66 | let convertedObject = try context.find(T.self, managedObjectID: strongSelf.observedObject.objectID) 67 | if let updatedObjects = (notification as NSNotification).userInfo?[NSUpdatedObjectsKey] as? NSSet { 68 | if updatedObjects.contains(convertedObject) { 69 | strongSelf.notifySubscribers(.updated(convertedObject)) 70 | } 71 | } 72 | 73 | if let refreshedObjects = (notification as NSNotification).userInfo?[NSRefreshedObjectsKey] as? NSSet { 74 | if refreshedObjects.contains(convertedObject) { 75 | strongSelf.notifySubscribers(.refreshed(convertedObject)) 76 | } 77 | } 78 | 79 | if let insertedObjects = (notification as NSNotification).userInfo?[NSInsertedObjectsKey] as? NSSet { 80 | if insertedObjects.contains(convertedObject) { 81 | strongSelf.notifySubscribers(.inserted(convertedObject)) 82 | } 83 | } 84 | 85 | if let deletedObjects = (notification as NSNotification).userInfo?[NSDeletedObjectsKey] as? NSSet { 86 | if deletedObjects.contains(convertedObject) { 87 | strongSelf.notifySubscribers(.deleted) 88 | } 89 | } 90 | } 91 | catch { 92 | } 93 | } 94 | } 95 | } 96 | 97 | deinit { 98 | if let notificationObserver = notificationObserver { 99 | NotificationCenter.default.removeObserver(notificationObserver) 100 | } 101 | } 102 | 103 | fileprivate func notifySubscribers(_ action: ObservedAction) { 104 | for subscriber in self.subscribers { 105 | subscriber(action) 106 | } 107 | } 108 | 109 | /** 110 | Subscribe a block that gets called when the observed object changes 111 | 112 | - parameter changeHandler: The handler to call on change 113 | 114 | - returns: Token you can use to unsubscribe 115 | */ 116 | public func subscribe(_ subscriber: @escaping Subscriber) -> Int { 117 | subscribers.append(subscriber) 118 | return subscribers.count - 1 119 | } 120 | 121 | /** 122 | Unsubscribe a previously subscribed block 123 | 124 | - parameter token: The token obtained when subscribing 125 | */ 126 | public func unsubscribe(token: Int) { 127 | subscribers[token] = { _ in } 128 | } 129 | 130 | @available(*, unavailable, renamed: "unsubscribe(token:)") 131 | public func unsubscribe(_ token: Int) { 132 | fatalError() 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /CoreDataKit/NSManagedObjectContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObjectContext.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 24-06-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | extension NSManagedObjectContext 12 | { 13 | /** 14 | Create a new `NSManagedObjectContext` that is directly associated with the given `NSPersistentStoreCoordinator`, will be of type `NSPrivateQueueConcurrencyType`. 15 | 16 | :discussion: The context will also obtain permanent IDs for `NSManagedObject`s before saving. This will prevent problems where you can't convert objects between two `NSManagedObjectContext`s, so it's advised to create contexts using this method. 17 | 18 | - parameter persistentStoreCoordinator: Persistent store coordinator to associate with 19 | 20 | - returns: Managed object context 21 | */ 22 | public convenience init(persistentStoreCoordinator: NSPersistentStoreCoordinator) 23 | { 24 | self.init(concurrencyType: .privateQueueConcurrencyType) 25 | performAndWait { [unowned self] in 26 | self.persistentStoreCoordinator = persistentStoreCoordinator 27 | self.undoManager = UndoManager() 28 | } 29 | // Moved the obtainPermanentIDsForInsertedObjects() call into the save methods itself to prevent deadlock scenarios 30 | // See commit 3bc30e1c59e395cf5b8b157842c24cc7e49a9edb 31 | // beginObtainingPermanentIDsForInsertedObjectsWhenContextWillSave() 32 | } 33 | 34 | /** 35 | Create a new `NSManagedObjectContext` that has the given context set as parent context to save to. 36 | 37 | :discussion: The context will also obtain permanent IDs for `NSManagedObject`s before saving. This will prevent problems where you can't convert objects between two `NSManagedObjectContext`s, so it's advised to create context using this method. 38 | 39 | - parameter concurrencyType: Concurrency type to use, must be `NSPrivateQueueConcurrencyType` or `NSMainQueueConcurrencyType` 40 | - parameter parentContext: Parent context to associate with 41 | 42 | - returns: Managed object context 43 | */ 44 | public convenience init(concurrencyType: NSManagedObjectContextConcurrencyType, parentContext: NSManagedObjectContext) 45 | { 46 | self.init(concurrencyType: concurrencyType) 47 | performAndWait { [unowned self] in 48 | self.parent = parentContext 49 | self.undoManager = UndoManager() 50 | } 51 | // Moved the obtainPermanentIDsForInsertedObjects() call into the save methods itself to prevent deadlock scenarios 52 | // See commit 3bc30e1c59e395cf5b8b157842c24cc7e49a9edb 53 | // beginObtainingPermanentIDsForInsertedObjectsWhenContextWillSave() 54 | } 55 | 56 | /** 57 | Creates child context with this context as its parent 58 | 59 | - returns: Child context 60 | */ 61 | public func createChildContext() -> NSManagedObjectContext { 62 | return NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType, parentContext: self) 63 | } 64 | 65 | // MARK: - Saving 66 | 67 | /** 68 | Performs the given block on a child context and persists changes performed on the given context to the persistent store. After saving the `CompletionHandler` block is called and passed a `NSError` object when an error occured or nil when saving was successfull. The `CompletionHandler` will always be called on the thread the context performs it's operations on. 69 | 70 | :discussion: Do not nest save operations with this method, since the nested save will also save to the persistent store this will give unexpected results. Also the nested calls will not perform their changes on nested contexts, so the changes will not appear in the outer call as you'd expect to. 71 | 72 | :discussion: Please remember that `NSManagedObjects` are not threadsafe and your block is performed on another thread/`NSManagedObjectContext`. Make sure to **always** convert your `NSManagedObjects` to the given `NSManagedObjectContext` with `NSManagedObject.inContext()` or by looking up the `NSManagedObjectID` in the given context. This prevents disappearing data. 73 | 74 | - parameter block: Block that performs the changes on the given context that should be saved 75 | - parameter completion: Completion block to run after changes are saved 76 | */ 77 | public func perform(block: @escaping PerformBlock, completionHandler: PerformBlockCompletionHandler? = nil) { 78 | perform { 79 | self.undoManager?.beginUndoGrouping() 80 | let commitAction = block(self) 81 | self.undoManager?.endUndoGrouping() 82 | 83 | switch commitAction { 84 | case .doNothing: 85 | completionHandler?({ commitAction }) 86 | 87 | case .saveToParentContext: 88 | do { 89 | try self.obtainPermanentIDsForInsertedObjects() 90 | try self.save() 91 | completionHandler?({ commitAction }) 92 | } catch { 93 | completionHandler?({ throw CoreDataKitError.coreDataError(error) }) 94 | } 95 | 96 | case .saveToPersistentStore: 97 | self.saveToPersistentStore { arg in 98 | completionHandler?({ try arg(); return commitAction }) 99 | } 100 | 101 | case .undo: 102 | self.undo() 103 | completionHandler?({ commitAction }) 104 | 105 | case .rollbackAllChanges: 106 | self.rollback() 107 | completionHandler?({ commitAction }) 108 | } 109 | } 110 | } 111 | 112 | @available(*, unavailable, renamed: "perform(block:completionHandler:)") 113 | public func performBlock(_ block: @escaping PerformBlock, completionHandler: PerformBlockCompletionHandler? = nil) { 114 | } 115 | 116 | /** 117 | Save all changes in this context and all parent contexts to the persistent store, `CompletionHandler` will be called when finished. 118 | 119 | :discussion: Must be called from a perform block action 120 | 121 | - parameter completionHandler: Completion block to run after changes are saved 122 | */ 123 | func saveToPersistentStore(_ completionHandler: CompletionHandler? = nil) 124 | { 125 | do { 126 | try obtainPermanentIDsForInsertedObjects() 127 | try save() 128 | 129 | if let parentContext = self.parent { 130 | parentContext.perform { 131 | parentContext.saveToPersistentStore(completionHandler) 132 | } 133 | } 134 | else { 135 | completionHandler?({}) 136 | } 137 | } catch let error as NSError { 138 | completionHandler?({ throw error }) 139 | } 140 | } 141 | 142 | // MARK: Obtaining permanent IDs 143 | 144 | /// Installs a notification handler on the will save event that calls `obtainPermanentIDsForInsertedObjects()` 145 | func beginObtainingPermanentIDsForInsertedObjectsWhenContextWillSave() 146 | { 147 | NotificationCenter.default.addObserver(self, selector: #selector(NSManagedObjectContext.obtainPermanentIDsForInsertedObjectsOnContextWillSave(_:)), name: NSNotification.Name.NSManagedObjectContextWillSave, object: self) 148 | } 149 | 150 | @objc func obtainPermanentIDsForInsertedObjectsOnContextWillSave(_ notification: Notification) 151 | { 152 | do { 153 | try obtainPermanentIDsForInsertedObjects() 154 | } 155 | catch { 156 | } 157 | } 158 | 159 | /** 160 | Obtains permanent object IDs for all objects inserted in this context. This ensures that the object has an object ID that you can lookup in an other context. 161 | 162 | @discussion This method is called automatically by `NSManagedObjectContext`s that are created by CoreDataKit right before saving. So usually you don't have to use this yourself if you stay within CoreDataKit created contexts. 163 | */ 164 | public func obtainPermanentIDsForInsertedObjects() throws { 165 | if (self.insertedObjects.count > 0) { 166 | do { 167 | try self.obtainPermanentIDs(for: Array(self.insertedObjects)) 168 | } 169 | catch { 170 | throw CoreDataKitError.coreDataError(error) 171 | } 172 | } 173 | } 174 | 175 | // MARK: - Creating 176 | 177 | /** 178 | Create and insert an entity into this context 179 | 180 | - parameter entity: Type of entity to create 181 | 182 | - returns: Result with the created entity 183 | */ 184 | public func create(_ entity: T.Type) throws -> T where T:NamedManagedObject 185 | { 186 | let desc = try entityDescription(entity) 187 | return try self.create(desc) 188 | } 189 | 190 | /** 191 | Create and insert entity into this context based on its description 192 | 193 | - parameter entityDescription: Description of the entity to create 194 | 195 | - returns: Result with the created entity 196 | */ 197 | func create(_ entityDescription: NSEntityDescription) throws -> T 198 | { 199 | if let entityName = entityDescription.name { 200 | return NSEntityDescription.insertNewObject(forEntityName: entityName, into: self) as! T 201 | } 202 | 203 | let error = CoreDataKitError.contextError(description: "Entity description '\(entityDescription)' has no name") 204 | throw error 205 | } 206 | 207 | /** 208 | Get description of an entity 209 | 210 | - parameter entity: Type of entity to describe 211 | 212 | - returns: Result with entity description of the given type 213 | */ 214 | func entityDescription(_ entity: T.Type) throws -> NSEntityDescription where T:NamedManagedObject 215 | { 216 | if let entityDescription = NSEntityDescription.entity(forEntityName: entity.entityName, in: self) { 217 | return entityDescription 218 | } 219 | 220 | throw CoreDataKitError.contextError(description: "Entity description for entity name '\(entity.entityName)' not found") 221 | } 222 | 223 | // MARK: - Deleting 224 | 225 | /** 226 | Delete object from this context 227 | 228 | - parameter managedObject: Object to delete 229 | 230 | - returns: Result wheter the delete was successful 231 | */ 232 | public func deleteWithPermanentID(_ managedObject: NSManagedObject) throws { 233 | do { 234 | try self.obtainPermanentIDs(for: [managedObject]) 235 | } 236 | catch { 237 | throw CoreDataKitError.coreDataError(error) 238 | } 239 | 240 | delete(managedObject) 241 | } 242 | 243 | // MARK: - Fetching 244 | 245 | /** 246 | Create a fetch request 247 | 248 | - parameter entity: Type of entity to search for 249 | - parameter predicate: Predicate to filter on 250 | - parameter sortDescriptors: Sort descriptors to sort on 251 | - parameter limit: Maximum number of items to return 252 | - parameter offset: The number of items to skip in the result 253 | 254 | - returns: Result with NSFetchRequest configured with the given parameters 255 | */ 256 | public func createFetchRequest(entity: T.Type, predicate: NSPredicate? = nil, sortDescriptors: [NSSortDescriptor]? = nil, limit: Int? = nil, offset: Int? = nil) throws -> NSFetchRequest where T:NamedManagedObject { 257 | let desc = try entityDescription(entity) 258 | return self.createFetchRequest(entityDescription: desc, predicate: predicate, sortDescriptors: sortDescriptors, limit: limit, offset: offset) 259 | } 260 | 261 | @available(*, unavailable, renamed: "createFetchRequest(entity:)") 262 | public func createFetchRequest(_ entity: T.Type, predicate: NSPredicate? = nil, sortDescriptors: [NSSortDescriptor]? = nil, limit: Int? = nil, offset: Int? = nil) throws -> NSFetchRequest where T:NamedManagedObject { 263 | fatalError() 264 | } 265 | 266 | /** 267 | Create a fetch request 268 | 269 | - parameter entity: Type of entity to search for 270 | - parameter predicate: Predicate to filter on 271 | - parameter sortDescriptors: Sort descriptors to sort on 272 | - parameter limit: Maximum number of items to return 273 | - parameter offset: The number of items to skip in the result 274 | 275 | - returns: NSFetchRequest configured with the given parameters 276 | */ 277 | public func createFetchRequest(entityDescription: NSEntityDescription, predicate: NSPredicate? = nil, sortDescriptors: [NSSortDescriptor]? = nil, limit: Int? = nil, offset: Int? = nil) -> NSFetchRequest { 278 | let fetchRequest = NSFetchRequest() 279 | fetchRequest.entity = entityDescription 280 | fetchRequest.predicate = predicate 281 | fetchRequest.sortDescriptors = sortDescriptors 282 | fetchRequest.fetchLimit = limit ?? 0 283 | fetchRequest.fetchOffset = offset ?? 0 284 | fetchRequest.returnsObjectsAsFaults = true 285 | 286 | return fetchRequest 287 | } 288 | 289 | @available(*, unavailable, renamed: "createFetchRequest(entityDescription:)") 290 | public func createFetchRequest(_ entityDescription: NSEntityDescription, predicate: NSPredicate? = nil, sortDescriptors: [NSSortDescriptor]? = nil, limit: Int? = nil, offset: Int? = nil) -> NSFetchRequest { 291 | fatalError() 292 | } 293 | 294 | /** 295 | Execute a fetch request 296 | 297 | - parameter fetchRequest: The request to execute on this context 298 | 299 | - returns: Result with array of entities found, empty array on no results 300 | */ 301 | public func execute(fetchRequest: NSFetchRequest) throws -> [T] { 302 | do { 303 | return try fetch(fetchRequest) 304 | } 305 | catch { 306 | throw CoreDataKitError.coreDataError(error) 307 | } 308 | } 309 | 310 | @available(*, unavailable, renamed: "execute(fetchRequest:)") 311 | public func executeFetchRequest(_ fetchRequest: NSFetchRequest) throws -> [T] { 312 | fatalError() 313 | } 314 | 315 | // MARK: Fetched result controller 316 | 317 | /** 318 | Create a fetched results controller 319 | 320 | :discussion: Be aware that when you change to request but use the same cache as before stuff can mess up! 321 | 322 | - parameter fetchRequest: Underlaying fetch request for the controller 323 | - parameter delegate: Delegate, the controller will only observe changes when a delegate is present 324 | - parameter sectionNameKeyPath: Keypath to section the results on 325 | - parameter cacheName: Name of the cache to use, nil for no cache 326 | 327 | - returns: Fetched results controller that already has performed the fetch 328 | */ 329 | public func fetchedResultsController(fetchRequest: NSFetchRequest, delegate: NSFetchedResultsControllerDelegate? = nil, sectionNameKeyPath: String? = nil, cacheName: String? = nil) throws -> NSFetchedResultsController { 330 | let resultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName) 331 | resultsController.delegate = delegate 332 | 333 | var error: Error? 334 | 335 | performAndWait { 336 | do { 337 | try resultsController.performFetch() 338 | } catch let err { 339 | error = err 340 | } 341 | } 342 | 343 | if let error = error { 344 | throw CoreDataKitError.coreDataError(error) 345 | } 346 | 347 | return resultsController 348 | } 349 | 350 | @available(*, unavailable, renamed: "fetchedResultsController(fetchRequest:)") 351 | public func fetchedResultsController(_ fetchRequest: NSFetchRequest, delegate: NSFetchedResultsControllerDelegate? = nil, sectionNameKeyPath: String? = nil, cacheName: String? = nil) throws -> NSFetchedResultsController { 352 | fatalError() 353 | } 354 | 355 | // MARK: Find helpers 356 | 357 | /** 358 | Looks the given managed object up in this context 359 | 360 | - parameter managedObject: Object from other context 361 | 362 | - returns: Result with the given object in this context 363 | */ 364 | public func find(_ entity: T.Type, managedObjectID: NSManagedObjectID) throws -> T { 365 | 366 | // First make sure we have a permanent ID for this object 367 | // if (managedObjectID.temporaryID) { 368 | // obtainPermanentIDsForObjects([managedObject], error: &optionalError) 369 | // 370 | // if let error = optionalError { 371 | // return Result(error) 372 | // } 373 | // } 374 | 375 | do { 376 | let managedObjectInContext = try existingObject(with: managedObjectID) 377 | return managedObjectInContext as! T 378 | } 379 | catch { 380 | throw CoreDataKitError.coreDataError(error) 381 | } 382 | } 383 | 384 | /** 385 | Find entities of a certain type in this context 386 | 387 | - parameter entity: Type of entity to search for 388 | - parameter predicate: Predicate to filter on 389 | - parameter sortDescriptors: Sort descriptors to sort on 390 | - parameter limit: Maximum number of items to return 391 | - parameter offset: The number of items to skip in the result 392 | 393 | - returns: Result with array of entities found, empty array on no results 394 | */ 395 | public func find(_ entity: T.Type, predicate: NSPredicate? = nil, sortDescriptors: [NSSortDescriptor]? = nil, limit: Int? = nil, offset: Int? = nil) throws -> [T] where T:NamedManagedObject { 396 | let desc = try entityDescription(entity) 397 | return try self.find(desc, predicate: predicate, sortDescriptors: sortDescriptors, limit: limit) 398 | } 399 | 400 | /** 401 | Find entities of a certain type in this context based on its description 402 | 403 | - parameter entity: Type of entity to search for 404 | - parameter predicate: Predicate to filter on 405 | - parameter sortDescriptors: Sort descriptors to sort on 406 | - parameter limit: Maximum number of items to return 407 | - parameter offset: The number of items to skip in the result 408 | 409 | - returns: Result with array of entities found, empty array on no results 410 | */ 411 | func find(_ entityDescription: NSEntityDescription, predicate: NSPredicate? = nil, sortDescriptors: [NSSortDescriptor]? = nil, limit: Int? = nil, offset: Int? = nil) throws -> [T] { 412 | let fetchRequest: NSFetchRequest = createFetchRequest(entityDescription: entityDescription, predicate: predicate, sortDescriptors: sortDescriptors, limit: limit) 413 | return try execute(fetchRequest: fetchRequest) 414 | } 415 | 416 | /** 417 | Get the first entity that matched the given parameters 418 | 419 | - parameter entity: Type of entity to search for 420 | - parameter predicate: Predicate to filter on 421 | - parameter sortDescriptors: Sort descriptors to sort on 422 | - parameter offset: The number of items to skip in the result 423 | 424 | - returns: Result with the entity or result with nil if the entity is not found 425 | */ 426 | public func findFirst(_ entity: T.Type, predicate: NSPredicate? = nil, sortDescriptors: [NSSortDescriptor]? = nil, offset: Int? = nil) throws -> T? where T:NamedManagedObject { 427 | let objects = try find(entity, predicate: predicate, sortDescriptors: sortDescriptors, limit: 1, offset: offset) 428 | return objects.first 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /CoreDataKit/NSPersistentStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSPersistentStore.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 07-07-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | extension NSPersistentStore 12 | { 13 | /** 14 | Creates URL for SQLite store with the given store name. 15 | 16 | - parameter storeName: Store name to build URL for 17 | 18 | - returns: URL with the location of the store 19 | */ 20 | public class func url(forSQLiteStoreName storeName: String) -> URL? 21 | { 22 | assert(storeName.characters.count > 0, "Store name must be longer then zero characters.") 23 | 24 | do { 25 | let supportDirectoryURL = try FileManager.default.url(for: .applicationSupportDirectory, in: .allDomainsMask, appropriateFor: nil, create: true) 26 | return supportDirectoryURL.appendingPathComponent(storeName + ".sqlite", isDirectory: false) 27 | } catch _ { 28 | return nil 29 | } 30 | } 31 | 32 | @available(*, unavailable, renamed: "url(forSQLiteStoreName:)") 33 | public class func URLForSQLiteStoreName(_ storeName: String) -> URL? { 34 | fatalError() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /CoreDataKit/NSPersistentStoreCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSPersistentStoreCoordinator.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 03-07-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | extension NSPersistentStoreCoordinator 12 | { 13 | /** 14 | Creates a `NSPersistentStoreCoordinator` with SQLite added as persistent store. 15 | 16 | :discussion: Use `NSPersistentStore.URLForSQLiteStoreName(storeName:)` to create the store URL 17 | 18 | - parameter automigrating: Whether to enable automigration for the SQLite store 19 | - parameter URL: URL to save the SQLite store at, pass nil to use default 20 | - parameter managedObjectModel: Managed object model to initialize the store with, pass nil to use all models in the main bundle 21 | */ 22 | public convenience init?(automigrating: Bool, deleteOnMismatch: Bool = false, URL optionalURL: URL? = nil, managedObjectModel optionalManagedObjectModel: NSManagedObjectModel? = nil) { 23 | 24 | // Fallback on the defaults 25 | let _managedObjectModel = optionalManagedObjectModel ?? NSManagedObjectModel.mergedModel(from: nil) 26 | let _url = optionalURL ?? NSPersistentStore.url(forSQLiteStoreName: "CoreDataKit") 27 | 28 | // Initialize coordinator if we have all data 29 | if let managedObjectModel = _managedObjectModel, let url = _url { 30 | self.init(managedObjectModel: managedObjectModel) 31 | self.addSQLitePersistentStore(at: url, automigrating: automigrating, deleteOnMismatch: deleteOnMismatch) 32 | } 33 | else { 34 | self.init() 35 | return nil 36 | } 37 | } 38 | 39 | /** 40 | Creates a `NSPersistentStoreCoordinator` with in memory store as backing store. 41 | 42 | - parameter managedObjectModel: Managed object model to initialize the store with, pass nil to use all models in the main bundle 43 | */ 44 | public convenience init(managedObjectModel optionalManagedObjectModel: NSManagedObjectModel?) throws { 45 | // Fallback on the defaults 46 | let _managedObjectModel = optionalManagedObjectModel ?? NSManagedObjectModel.mergedModel(from: nil) 47 | 48 | // Initialize coordinator if we have all data 49 | if let managedObjectModel = _managedObjectModel 50 | { 51 | self.init(managedObjectModel: managedObjectModel) 52 | do { 53 | try self.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil) 54 | } catch let error as NSError { 55 | throw error 56 | } 57 | } 58 | else 59 | { 60 | throw CoreDataKitError.unknownError(description: "NSMangedObjectModel should be available") 61 | } 62 | } 63 | 64 | // MARK: - Store add helpers 65 | 66 | /** 67 | Adds a SQLite persistent store to this persistent store coordinator. 68 | 69 | :discussion: Will do a async retry when automigration fails, because of a CoreData bug in serveral iOS versions where migration fails the first time. 70 | 71 | - parameter URL: Location of the store 72 | - parameter automigrating: Whether the store should automigrate itself 73 | */ 74 | fileprivate func addSQLitePersistentStore(at url: Foundation.URL, automigrating: Bool, deleteOnMismatch: Bool) 75 | { 76 | func addStore() throws { 77 | let options: [AnyHashable: Any] = [ 78 | NSMigratePersistentStoresAutomaticallyOption: automigrating, 79 | NSInferMappingModelAutomaticallyOption: automigrating, 80 | NSSQLitePragmasOption: ["journal_mode": "WAL"] 81 | ]; 82 | 83 | do { 84 | try self.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: options) 85 | } 86 | catch { 87 | throw CoreDataKitError.coreDataError(error) 88 | } 89 | } 90 | 91 | do { 92 | try addStore() 93 | } 94 | catch let error as NSError { 95 | // Check for version mismatch 96 | if (deleteOnMismatch && NSCocoaErrorDomain == error.domain && (NSPersistentStoreIncompatibleVersionHashError == error.code || NSMigrationMissingSourceModelError == error.code)) { 97 | 98 | CDK.sharedLogger(.warn, "Model mismatch, removing persistent store...") 99 | let urlString = url.absoluteString 100 | let shmFile = urlString + "-shm" 101 | let walFile = urlString + "-wal" 102 | 103 | do { 104 | try FileManager.default.removeItem(at: url) 105 | try FileManager.default.removeItem(atPath: shmFile) 106 | try FileManager.default.removeItem(atPath: walFile) 107 | } catch _ { 108 | } 109 | 110 | do { 111 | try addStore() 112 | } 113 | catch let error { 114 | CDK.sharedLogger(.error, "Failed to add SQLite persistent store: \(error)") 115 | } 116 | } 117 | // Workaround for "Migration failed after first pass" error 118 | else if automigrating { 119 | CDK.sharedLogger(.warn, "[CoreDataKit] Applying workaround for 'Migration failed after first pass' bug, retrying...") 120 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(NSEC_PER_SEC) / 2) / Double(NSEC_PER_SEC)) { 121 | do { 122 | try addStore() 123 | } 124 | catch let error { 125 | CDK.sharedLogger(.error, "Failed to add SQLite persistent store: \(error)") 126 | } 127 | } 128 | } 129 | else { 130 | CDK.sharedLogger(.error, "Failed to add SQLite persistent store: \(error)") 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /CoreDataKit/NamedManagedObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NamedManagedObject.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 17-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | /// Protocol that enables CoreDataKit to handle entities based on a 10 | public protocol NamedManagedObject: class { 11 | /// The name of the entity as it is known in the managed object model 12 | static var entityName: String { get } 13 | } 14 | -------------------------------------------------------------------------------- /CoreDataKit/TableViewFetchedResultsControllerDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSFetchedResultsControllerDelegate.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 29-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | /** 13 | Simple implementation of NSFetchedResultsControllerDelegate for use with a UITableView 14 | 15 | :discussion: Be aware that the NSFetchedResultsController will not retain your delegate object. So you have to keep a reference to this object somewhere. 16 | */ 17 | open class TableViewFetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate { 18 | var sectionAnimation: UITableViewRowAnimation = .automatic 19 | var rowAnimation: UITableViewRowAnimation = .automatic 20 | 21 | weak var tableView: UITableView? 22 | 23 | /** 24 | Initialize a delegate 25 | 26 | - parameter tableView: The table view to perform the changed the NSFetchedResultsController reports on 27 | */ 28 | public init(tableView: UITableView) { 29 | self.tableView = tableView 30 | } 31 | 32 | /// Implementation of NSFetchedResultsControllerDelegate 33 | open func controllerWillChangeContent(_ controller: NSFetchedResultsController) { 34 | tableView?.beginUpdates() 35 | } 36 | 37 | /// Implementation of NSFetchedResultsControllerDelegate 38 | open func controller(_ controller: NSFetchedResultsController, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) { 39 | switch type { 40 | case .insert: 41 | tableView?.insertSections(IndexSet(integer: sectionIndex), with: sectionAnimation) 42 | 43 | case .delete: 44 | tableView?.deleteSections(IndexSet(integer: sectionIndex), with: sectionAnimation) 45 | 46 | default: 47 | break // Noop 48 | } 49 | } 50 | 51 | /// Implementation of NSFetchedResultsControllerDelegate 52 | open func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { 53 | switch type { 54 | case .insert: 55 | tableView?.insertRows(at: [newIndexPath!], with: rowAnimation) 56 | 57 | case .delete: 58 | tableView?.deleteRows(at: [indexPath!], with: rowAnimation) 59 | 60 | case .move: 61 | tableView?.deleteRows(at: [indexPath!], with: rowAnimation) 62 | tableView?.insertRows(at: [newIndexPath!], with: rowAnimation) 63 | 64 | case .update: 65 | tableView?.reloadRows(at: [indexPath!], with: rowAnimation) 66 | } 67 | } 68 | 69 | /// Implementation of NSFetchedResultsControllerDelegate 70 | open func controllerDidChangeContent(_ controller: NSFetchedResultsController) { 71 | tableView?.endUpdates() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /CoreDataKit/Types.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Types.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 15-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | 12 | /// Commit actions that can be taken by CoreDataKit after a block of changes is performed 13 | public enum CommitAction { 14 | /// Do not do any save/rollback operation, just leave the changes on the context unsaved 15 | case doNothing 16 | 17 | /// Save all changes in this context to the parent context 18 | case saveToParentContext 19 | 20 | /// Save all changes in this and all parent contexts to the persistent store 21 | case saveToPersistentStore 22 | 23 | /// Undo changes done in the related PerformBlock, all other changes will remain untouched 24 | case undo 25 | 26 | /// Rollback all changes on the context, this will revert all unsaved changes in the context 27 | case rollbackAllChanges 28 | } 29 | 30 | /** 31 | Blocktype used to perform changes on a `NSManagedObjectContext`. 32 | 33 | - parameter context: The context to perform your changes on 34 | */ 35 | public typealias PerformBlock = (NSManagedObjectContext) -> CommitAction 36 | 37 | /** 38 | Blocktype used to handle completion. 39 | 40 | - parameter result: Wheter the operation was successful 41 | */ 42 | public typealias CompletionHandler = (_ arg: () throws -> Void) -> Void 43 | 44 | /** 45 | Blocktype used to handle completion of `PerformBlock`s. 46 | 47 | - parameter result: Wheter the operation was successful 48 | - parameter commitAction: The type of commit action the block has done 49 | */ 50 | public typealias PerformBlockCompletionHandler = (_ arg: () throws -> CommitAction) -> Void 51 | 52 | // MARK: - Errors 53 | 54 | /// All errors that can occure from CoreDataKit 55 | 56 | public enum CoreDataKitError : Error { 57 | case coreDataError(Error) 58 | 59 | case importCancelled(entityName: String) 60 | case importError(description: String) 61 | case contextError(description: String) 62 | case unimplementedMethod(description: String) 63 | 64 | case unknownError(description: String) 65 | } 66 | 67 | extension CoreDataKitError : CustomStringConvertible { 68 | public var description: String { 69 | switch self { 70 | case .coreDataError(let error): 71 | return "CoreDataError: \(error)" 72 | case .importCancelled(let entityName): 73 | return "Import of entity \(entityName) cancelled" 74 | case .contextError(let description): 75 | return description 76 | case .importError(let description): 77 | return description 78 | case .unimplementedMethod(let description): 79 | return description 80 | case .unknownError(let description): 81 | return "Unknown error: \(description)" 82 | } 83 | } 84 | } 85 | 86 | /// Wrapping CoreDataKitError in a NSError for compatibility with older NSError-based code 87 | 88 | public let CoreDataKitErrorDomain = "CoreDataKitErrorDomain" 89 | 90 | private let CoreDataKitErrorUserInfoErrorKey = "CoreDataKitErrorUserInfoErrorKey" 91 | 92 | public class CoreDataKitErrorBox : CustomStringConvertible { 93 | public let unbox: CoreDataKitError 94 | 95 | init(value: CoreDataKitError) { 96 | self.unbox = value 97 | } 98 | 99 | public var description: String { 100 | return unbox.description 101 | } 102 | } 103 | 104 | extension CoreDataKitError { 105 | 106 | public var nsError: NSError { 107 | switch self { 108 | case .coreDataError(let error): 109 | return error as NSError 110 | default: 111 | return NSError( 112 | domain: CoreDataKitErrorDomain, 113 | code: 0, 114 | userInfo: [CoreDataKitErrorUserInfoErrorKey: CoreDataKitErrorBox(value: self)]) 115 | } 116 | } 117 | } 118 | 119 | extension NSError { 120 | public var coreDataKitError: CoreDataKitError? { 121 | if let boxed = self.userInfo[CoreDataKitErrorUserInfoErrorKey] as? CoreDataKitErrorBox { 122 | return boxed.unbox 123 | } 124 | return nil 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /CoreDataKitTests/CoreDataKitTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataKitTests.swift 3 | // CoreDataKitTests 4 | // 5 | // Created by Mathijs Kadijk on 23-06-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import CoreData 11 | import CoreDataKit 12 | 13 | class CoreDataKitTests: TestCase { 14 | func testPersistentStoreCoordinator() { 15 | XCTAssertEqual(CDK.persistentStoreCoordinator, CDK.sharedStack!.persistentStoreCoordinator, "Incorrect persistent coordinator") 16 | } 17 | 18 | func testBackgroundContext() { 19 | XCTAssertNotNil(CDK.backgroundContext.persistentStoreCoordinator, "Missing persistent coordinator") 20 | XCTAssertEqual(CDK.backgroundContext.persistentStoreCoordinator!, CDK.persistentStoreCoordinator, "Incorrect persistent coordinator") 21 | XCTAssertNotNil(CDK.backgroundContext.parent, "Missing parent context") 22 | XCTAssertEqual(CDK.backgroundContext.parent!, CDK.sharedStack!.mainThreadContext, "Incorrect parent context") 23 | } 24 | 25 | func testMainThreadContext() { 26 | XCTAssertNotNil(CDK.mainThreadContext.persistentStoreCoordinator, "Missing persistent coordinator") 27 | XCTAssertEqual(CDK.mainThreadContext.persistentStoreCoordinator!, CDK.persistentStoreCoordinator, "Incorrect persistent coordinator") 28 | XCTAssertNotNil(CDK.mainThreadContext.parent, "Missing parent context") 29 | XCTAssertEqual(CDK.mainThreadContext.parent!, CDK.sharedStack!.rootContext, "Incorrect parent context") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /CoreDataKitTests/CoreDataStackTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataStackTests.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 16-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import CoreData 11 | import CoreDataKit 12 | 13 | class CoreDataStackTests: TestCase { 14 | func testRootContext() { 15 | XCTAssertNil(coreDataStack.rootContext.parent, "Unexpected parent context") 16 | XCTAssertNotNil(coreDataStack.rootContext.persistentStoreCoordinator, "Missing persistent coordinator") 17 | XCTAssertEqual(coreDataStack.rootContext.persistentStoreCoordinator!, coreDataStack.persistentStoreCoordinator, "Incorrect persistent coordinator") 18 | } 19 | 20 | func testMainThreadContext() { 21 | XCTAssertNotNil(coreDataStack.mainThreadContext.persistentStoreCoordinator, "Missing persistent coordinator") 22 | XCTAssertEqual(coreDataStack.mainThreadContext.persistentStoreCoordinator!, coreDataStack.persistentStoreCoordinator, "Incorrect persistent coordinator") 23 | XCTAssertNotNil(coreDataStack.mainThreadContext.parent, "Missing parent context") 24 | XCTAssertEqual(coreDataStack.mainThreadContext.parent!, coreDataStack.rootContext, "Incorrect parent context") 25 | } 26 | 27 | func testDumpStack() { 28 | coreDataStack.dumpStack() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CoreDataKitTests/Fixtures/Car.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Car.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 20-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import CoreDataKit 12 | 13 | class Car: NSManagedObject, NamedManagedObject { 14 | 15 | @NSManaged var plate: String 16 | @NSManaged var color: String 17 | @NSManaged var owners: NSSet 18 | 19 | class var entityName: String { return "Car" } 20 | } 21 | -------------------------------------------------------------------------------- /CoreDataKitTests/Fixtures/Employee.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Employee.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 15-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import CoreDataKit 12 | 13 | class Employee: NSManagedObject, NamedManagedObject { 14 | 15 | @NSManaged var name: String 16 | 17 | class var entityName: String { return "Employee" } 18 | } 19 | -------------------------------------------------------------------------------- /CoreDataKitTests/Fixtures/EmployeeImportable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmployeeImportable.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 18-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import CoreDataKit 12 | 13 | class EmployeeImportable: NSManagedObject, NamedManagedObject { 14 | 15 | @NSManaged var name: String 16 | @NSManaged var age: Int 17 | @NSManaged var haircolor: String? 18 | 19 | class var entityName: String { return "EmployeeImportable" } 20 | } 21 | -------------------------------------------------------------------------------- /CoreDataKitTests/Fixtures/EmployeeIncorrectEntityName.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmployeeIncorrectEntityName.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 17-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import CoreDataKit 12 | 13 | class EmployeeIncorrectEntityName: NSManagedObject, NamedManagedObject { 14 | 15 | @NSManaged var name: String 16 | 17 | class var entityName: String { return "EmployeeIncorrectEntityName" } 18 | } 19 | -------------------------------------------------------------------------------- /CoreDataKitTests/Fixtures/EmployeeWithRelationEmbedding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmployeeWithRelationEmbedding.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 28-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import CoreDataKit 12 | 13 | class EmployeeWithRelationEmbedding: NSManagedObject, NamedManagedObject { 14 | 15 | @NSManaged var name: String 16 | @NSManaged var salary: Salary 17 | 18 | class var entityName: String { return "EmployeeWithRelationEmbedding" } 19 | } 20 | -------------------------------------------------------------------------------- /CoreDataKitTests/Fixtures/EmployeeWithRelations.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmployeeWithRelations.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 20-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import CoreDataKit 12 | 13 | class EmployeeWithRelations: NSManagedObject, NamedManagedObject { 14 | 15 | @NSManaged var name: String 16 | @NSManaged var age: NSNumber 17 | @NSManaged var cars: NSMutableSet 18 | 19 | class var entityName: String { return "EmployeeWithRelations" } 20 | } 21 | -------------------------------------------------------------------------------- /CoreDataKitTests/Fixtures/Employees.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "name": "A", "age": 32, "checkName": "A", "checkAge": 32 }, 3 | { "naam": "B", "name": "WRONG NAME FIELD: name", "age": 33, "checkName": "B", "checkAge": 33 }, 4 | { "nome": "C", "naam": "WRONG NAME FIELD: naam", "name": "WRONG NAME FIELD: name", "age": 34, "checkName": "C", "checkAge": 34 }, 5 | ] -------------------------------------------------------------------------------- /CoreDataKitTests/Fixtures/EmployeesNestedEmbeddingRelation.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Jessica Pearson", 3 | "salary": { 4 | "amount": 123456789 5 | } 6 | } -------------------------------------------------------------------------------- /CoreDataKitTests/Fixtures/EmployeesNestedRelation.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mike Ross", 3 | "age": 32, 4 | "car": { 5 | "plate": "YY-YY-YY", 6 | "color": "red" 7 | } 8 | } -------------------------------------------------------------------------------- /CoreDataKitTests/Fixtures/EmployeesReferencedRelation.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mike Ross", 3 | "age": 23, 4 | "car": "ZZ-ZZ-ZZ" 5 | } -------------------------------------------------------------------------------- /CoreDataKitTests/Fixtures/EmployeesWithNull.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Haircolor Nulled", 3 | "age": 100, 4 | "haircolor": null 5 | } -------------------------------------------------------------------------------- /CoreDataKitTests/Fixtures/EmployeesWithOmittedField.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Haircolor Omitted", 3 | "age": 200 4 | } -------------------------------------------------------------------------------- /CoreDataKitTests/Fixtures/Model.xcdatamodeld/Model.xcdatamodel/contents: -------------------------------------------------------------------------------- 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 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 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 | -------------------------------------------------------------------------------- /CoreDataKitTests/Fixtures/Salary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Salary.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 28-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import CoreDataKit 12 | 13 | class Salary: NSManagedObject, NamedManagedObject { 14 | 15 | @NSManaged var amount: NSDecimalNumber 16 | @NSManaged var employee: EmployeeWithRelationEmbedding 17 | 18 | class var entityName: String { return "Salary" } 19 | } 20 | -------------------------------------------------------------------------------- /CoreDataKitTests/Importing/CoreDataStack+ImportingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataStack+ImportingTests.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 05-11-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import CoreData 11 | import CoreDataKit 12 | 13 | class CoreDataStackImportingTests: TestCase { 14 | func testDumpImportConfiguration() { 15 | coreDataStack.dumpImportConfiguration() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CoreDataKitTests/Importing/NSManagedObject+ImportingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObject+Importing.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 18-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import CoreData 11 | import CoreDataKit 12 | 13 | class NSManagedObjectTests: TestCase { 14 | func testImportDictionary() { 15 | guard let jsonArray = loadJSONFile(filename: "Employees") as? [[String: AnyObject]] else { 16 | XCTFail("Missing Employees.json fixture") 17 | return 18 | } 19 | 20 | for jsonObject in jsonArray { 21 | do { 22 | let result = try coreDataStack.rootContext.importEntity(EmployeeImportable.self, dictionary: jsonObject) 23 | 24 | XCTAssertEqual(result.name, jsonObject["checkName"] as? String, "Unexpected name") 25 | XCTAssertEqual(result.age, jsonObject["checkAge"] as? Int, "Unexpected age") 26 | } 27 | catch { 28 | XCTFail("Unexpected error") 29 | } 30 | } 31 | } 32 | 33 | func testImportDictionaryWithNull() { 34 | 35 | guard let jsonObject = loadJSONFile(filename: "EmployeesWithNull") as? [String: AnyObject] else { 36 | XCTFail("Missing EmployeesWithNull.json fixture") 37 | return 38 | } 39 | 40 | do { 41 | let object = try coreDataStack.rootContext.create(EmployeeImportable.self) 42 | object.name = "Haircolor Nulled" 43 | object.age = 100 44 | object.haircolor = "Yellow" 45 | 46 | let imported = try coreDataStack.rootContext.importEntity(EmployeeImportable.self, dictionary: jsonObject) 47 | XCTAssertEqual(imported.name, "Haircolor Nulled", "Unexpected name") 48 | XCTAssertEqual(imported.age, 100, "Unexpected age") 49 | XCTAssertNil(imported.haircolor, "Unexpected haircolor") 50 | } 51 | catch { 52 | XCTFail("Unexpected error") 53 | } 54 | } 55 | 56 | func testImportDictionaryWithOmittedField() { 57 | 58 | guard let jsonObject = loadJSONFile(filename: "EmployeesWithOmittedField") as? [String: AnyObject] else { 59 | XCTFail("Missing EmployeesWithOmittedField.json fixture") 60 | return 61 | } 62 | 63 | do { 64 | let object = try coreDataStack.rootContext.create(EmployeeImportable.self) 65 | object.name = "Haircolor Omitted" 66 | object.age = 100 67 | object.haircolor = "Yellow" 68 | 69 | let imported = try coreDataStack.rootContext.importEntity(EmployeeImportable.self, dictionary: jsonObject) 70 | XCTAssertEqual(imported.name, "Haircolor Omitted", "Unexpected name") 71 | XCTAssertEqual(imported.age, 200, "Unexpected age") 72 | if let haircolor = imported.haircolor { 73 | XCTAssertEqual(haircolor, "Yellow", "Unexpected haircolor") 74 | } else { 75 | XCTFail("Missing haircolor") 76 | } 77 | } 78 | catch { 79 | XCTFail("Unexpected error") 80 | } 81 | } 82 | 83 | func testImportWithNestedRelation() { 84 | 85 | guard let jsonObject = loadJSONFile(filename: "EmployeesNestedRelation") as? [String: AnyObject] else { 86 | XCTFail("Missing EmployeesNestedRelation.json fixture") 87 | return 88 | } 89 | 90 | do { 91 | let imported = try coreDataStack.rootContext.importEntity(EmployeeWithRelations.self, dictionary: jsonObject) 92 | XCTAssertEqual(imported.name, "Mike Ross", "Unexpected name") 93 | XCTAssertEqual(imported.age, 32, "Unexpected age") 94 | XCTAssertEqual(imported.cars.count, 1, "Unexpected count of cars") 95 | 96 | if let car = imported.cars.anyObject() as? Car { 97 | XCTAssertEqual(car.plate, "YY-YY-YY", "Unexpected plate") 98 | XCTAssertEqual(car.color, "red", "Unexpected color") 99 | } 100 | } 101 | catch { 102 | XCTFail("Unexpected error") 103 | } 104 | } 105 | 106 | func testImportWithExistingReferencedRelation() { 107 | 108 | guard let jsonObject = loadJSONFile(filename: "EmployeesReferencedRelation") as? [String: AnyObject] else { 109 | XCTFail("Missing EmployeesReferencedRelation.json fixture") 110 | return 111 | } 112 | 113 | do { 114 | let object = try coreDataStack.rootContext.create(Car.self) 115 | object.plate = "ZZ-ZZ-ZZ" 116 | object.color = "brown" 117 | 118 | let imported = try coreDataStack.rootContext.importEntity(EmployeeWithRelations.self, dictionary: jsonObject) 119 | 120 | XCTAssertEqual(imported.name, "Mike Ross", "Unexpected name") 121 | XCTAssertEqual(imported.age, 23, "Unexpected age") 122 | XCTAssertEqual(imported.cars.count, 1, "Unexpected count of cars") 123 | 124 | if let car = imported.cars.anyObject() as? Car { 125 | XCTAssertEqual(car.plate, "ZZ-ZZ-ZZ", "Unexpected plate") 126 | XCTAssertEqual(car.color, "brown", "Unexpected color") 127 | } 128 | } 129 | catch { 130 | XCTFail("Unexpected error") 131 | } 132 | } 133 | 134 | func testImportWithUnexistingReferencedRelation() { 135 | 136 | guard let jsonObject = loadJSONFile(filename: "EmployeesReferencedRelation") as? [String: AnyObject] else { 137 | XCTFail("Missing EmployeesReferencedRelation.json fixture") 138 | return 139 | } 140 | 141 | do { 142 | let imported = try coreDataStack.rootContext.importEntity(EmployeeWithRelations.self, dictionary: jsonObject) 143 | XCTAssertEqual(imported.name, "Mike Ross", "Unexpected name") 144 | XCTAssertEqual(imported.age, 23, "Unexpected age") 145 | XCTAssertEqual(imported.cars.count, 0, "Unexpected count of cars") 146 | 147 | if let car = imported.cars.anyObject() as? Car { 148 | XCTAssertEqual(car.plate, "YY-YY-YY", "Unexpected plate") 149 | XCTAssertEqual(car.color, "red", "Unexpected color") 150 | } 151 | } 152 | catch { 153 | XCTFail("Unexpected error") 154 | } 155 | } 156 | 157 | func testImportNestedEmbeddingRelation() { 158 | 159 | guard let jsonObject = loadJSONFile(filename: "EmployeesNestedEmbeddingRelation") as? [String: AnyObject] else { 160 | XCTFail("Missing EmployeesNestedEmbeddingRelation.json fixture") 161 | return 162 | } 163 | 164 | do { 165 | let imported = try coreDataStack.rootContext.importEntity(EmployeeWithRelationEmbedding.self, dictionary: jsonObject) 166 | XCTAssertEqual(imported.name, "Jessica Pearson", "Unexpected name") 167 | XCTAssertEqual(imported.salary.amount, 123456789, "Unexpected salary") 168 | } 169 | catch { 170 | XCTFail("Unexpected error") 171 | } 172 | } 173 | 174 | /// This test covers https://github.com/mac-cain13/CoreDataKit/issues/4 175 | func testOldValueIsDeletedWhenEmbeddingRelationIsUpdated() { 176 | 177 | guard let jsonObject = loadJSONFile(filename: "EmployeesNestedEmbeddingRelation") as? [String: AnyObject] else { 178 | XCTFail("Missing EmployeesNestedEmbeddingRelation.json fixture") 179 | return 180 | } 181 | 182 | do { 183 | _ = try coreDataStack.rootContext.importEntity(EmployeeWithRelationEmbedding.self, dictionary: jsonObject) 184 | _ = try coreDataStack.rootContext.importEntity(EmployeeWithRelationEmbedding.self, dictionary: jsonObject) 185 | 186 | let results = try coreDataStack.rootContext.find(Salary.self) 187 | XCTAssertEqual(results.count, 1, "Unexpected Salary count") 188 | } 189 | catch { 190 | XCTFail("Unexpected error") 191 | } 192 | } 193 | 194 | private func loadJSONFile(filename: String) -> Any? { 195 | for bundle in Bundle.allBundles { 196 | if let fileURL = bundle.url(forResource: filename, withExtension: "json") { 197 | do { 198 | let data = try Data(contentsOf: fileURL) 199 | let json = try JSONSerialization.jsonObject(with: data, options: []) 200 | return json 201 | } catch let error as NSError { 202 | XCTFail("Unexpected error while decoding JSON: \(error)") 203 | } 204 | } 205 | } 206 | 207 | return nil 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /CoreDataKitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /CoreDataKitTests/ManagedObjectObserverTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ManagedObjectObserverTests.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 01-11-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | import CoreData 12 | import CoreDataKit 13 | 14 | class ManagedObjectObserverTests: TestCase { 15 | // func testSubscribersCalled() { 16 | // let calledExpectation = expectationWithDescription("Subscriber not called") 17 | // 18 | // var observer: ManagedObjectObserver? 19 | // var observable: Employee! 20 | // 21 | // coreDataStack.performBlockOnBackgroundContext({ context in 22 | // observable = context.create(Employee.self).value() 23 | // observable.name = "Scottie" 24 | // 25 | // return .SaveToPersistentStore 26 | // }, completionHandler: { _ in 27 | // observable = self.coreDataStack.mainThreadContext.find(observable).value() 28 | // 29 | // observer = ManagedObjectObserver(observeObject: observable as Employee, inContext: self.coreDataStack.mainThreadContext) 30 | // observer?.subscribe { observedAction in 31 | // XCTAssertEqual(observedAction.value()!.name, "Dana J. Scott", "Unexpected name") 32 | // calledExpectation.fulfill() 33 | // } 34 | // 35 | // self.coreDataStack.performBlockOnBackgroundContext { context in 36 | // observable.name = "Dana J. Scott" 37 | // return .SaveToPersistentStore 38 | // } 39 | // }) 40 | // 41 | // waitForExpectationsWithTimeout(3, handler: nil) 42 | // } 43 | // 44 | // func testSubscribersCalledWithObjectOnRootContext() { 45 | // let calledExpectation = expectationWithDescription("Subscriber not called") 46 | // 47 | // var observer: ManagedObjectObserver? 48 | // var observable: Employee! 49 | // 50 | // coreDataStack.performBlockOnBackgroundContext({ context in 51 | // observable = context.create(Employee.self).value() 52 | // observable.name = "Scottie" 53 | // 54 | // return .SaveToPersistentStore 55 | // }, completionHandler: { _ in 56 | // observable = self.coreDataStack.rootContext.find(observable).value() 57 | // 58 | // observer = ManagedObjectObserver(observeObject: observable as Employee, inContext: self.coreDataStack.mainThreadContext) 59 | // observer?.subscribe { object in 60 | // XCTAssertEqual(object.value()!.name, "Dana J. Scott", "Unexpected name") 61 | // calledExpectation.fulfill() 62 | // } 63 | // 64 | // self.coreDataStack.performBlockOnBackgroundContext { context in 65 | // observable.name = "Dana J. Scott" 66 | // return .SaveToPersistentStore 67 | // } 68 | // }) 69 | // 70 | // waitForExpectationsWithTimeout(3, handler: nil) 71 | // } 72 | // 73 | // func testNoSubscribers() { 74 | // let calledExpectation = expectationWithDescription("Subscriber not called") 75 | // 76 | // var observable: Employee! 77 | // 78 | // coreDataStack.performBlockOnBackgroundContext({ context in 79 | // observable = context.create(Employee.self).value() 80 | // observable.name = "Scottie" 81 | // 82 | // return .SaveToPersistentStore 83 | // }, completionHandler: { _ in 84 | // observable = self.coreDataStack.mainThreadContext.find(observable).value() 85 | // 86 | // let observer = ManagedObjectObserver(observeObject: observable as Employee, inContext: self.coreDataStack.mainThreadContext) 87 | // 88 | // self.coreDataStack.performBlockOnBackgroundContext({ context in 89 | // observable.name = "Dana J. Scott" 90 | // return .SaveToPersistentStore 91 | // }, completionHandler: { _ in 92 | // calledExpectation.fulfill() 93 | // }) 94 | // }) 95 | // 96 | // waitForExpectationsWithTimeout(3, handler: nil) 97 | // } 98 | // 99 | // func testUnsubscribe() { 100 | // let calledExpectation = expectationWithDescription("Subscriber not called") 101 | // 102 | // var observable: Employee! 103 | // 104 | // coreDataStack.performBlockOnBackgroundContext({ context in 105 | // observable = context.create(Employee.self).value() 106 | // observable.name = "Scottie" 107 | // 108 | // return .SaveToPersistentStore 109 | // }, completionHandler: { _ in 110 | // observable = self.coreDataStack.mainThreadContext.find(observable).value() 111 | // 112 | // let observer = ManagedObjectObserver(observeObject: observable as Employee, inContext: self.coreDataStack.mainThreadContext) 113 | // let token = observer.subscribe { object in 114 | // XCTFail("Unsubscribed subscriber called") 115 | // } 116 | // observer.unsubscribe(token) 117 | // 118 | // self.coreDataStack.performBlockOnBackgroundContext({ context in 119 | // observable.name = "Dana J. Scott" 120 | // return .SaveToPersistentStore 121 | // }, completionHandler: { _ in 122 | // calledExpectation.fulfill() 123 | // }) 124 | // }) 125 | // 126 | // waitForExpectationsWithTimeout(3, handler: nil) 127 | // } 128 | } 129 | -------------------------------------------------------------------------------- /CoreDataKitTests/NSManagedObjectContextTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObjectTests.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 15-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import CoreData 11 | import CoreDataKit 12 | 13 | class NSManagedObjectContextTests: TestCase { 14 | func testInitWithPersistentStore() { 15 | let context = NSManagedObjectContext(persistentStoreCoordinator: coreDataStack.persistentStoreCoordinator) 16 | 17 | XCTAssertNil(context.parent, "Unexpected parent context") 18 | XCTAssertNotNil(context.persistentStoreCoordinator, "Missing persistent coordinator") 19 | XCTAssertEqual(context.persistentStoreCoordinator!, coreDataStack.persistentStoreCoordinator, "Incorrect persistent coordinator") 20 | XCTAssertEqual(context.concurrencyType, NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType, "Incorrect concurrency type") 21 | } 22 | 23 | func testInitWithPersistentStoreObtainsPermanentIDs() { 24 | let context = NSManagedObjectContext(persistentStoreCoordinator: coreDataStack.persistentStoreCoordinator) 25 | testContextObtainsPermanentIDs(context: context) 26 | } 27 | 28 | func testInitWithParentContext() { 29 | let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType, parentContext: coreDataStack.rootContext) 30 | 31 | XCTAssertNotNil(context.parent, "Missing parent context") 32 | XCTAssertEqual(context.parent!, coreDataStack.rootContext, "Incorrect parent context") 33 | XCTAssertEqual(context.concurrencyType, NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType, "Incorrect concurrency type") 34 | } 35 | 36 | func testInitWithParentContextObtainsPermanentIDs() { 37 | let parentContext = NSManagedObjectContext(persistentStoreCoordinator: coreDataStack.persistentStoreCoordinator) 38 | let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType, parentContext: parentContext) 39 | testContextObtainsPermanentIDs(context: context) 40 | } 41 | 42 | // MARK: - Saving 43 | 44 | func testPerformBlockAndSaveToPersistentStore() { 45 | let completionExpectation = expectation(description: "Expected completion handler call") 46 | 47 | let countFRq = NSFetchRequest(entityName: "Employee") 48 | XCTAssertEqual(try? coreDataStack.rootContext.count(for: countFRq), 0, "Unexpected employee entities") 49 | 50 | coreDataStack.backgroundContext.perform(block: { (context) -> CommitAction in 51 | let employee: Employee = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: context) as! Employee 52 | employee.name = "Mike Ross" 53 | 54 | return .saveToPersistentStore 55 | }, completionHandler: { (result) -> Void in 56 | do { 57 | _ = try result() 58 | XCTAssertEqual(try self.coreDataStack.rootContext.count(for: countFRq), 1, "Unexpected employee entity count") 59 | completionExpectation.fulfill() 60 | } 61 | catch { 62 | XCTFail("Unexpected error") 63 | } 64 | }) 65 | 66 | waitForExpectations(timeout: 3, handler: nil) 67 | } 68 | 69 | func testFailingPerformBlockAndSaveToPersistentStore() { 70 | let completionExpectation = expectation(description: "Expected completion handler call") 71 | 72 | let countFRq = NSFetchRequest(entityName: "Employee") 73 | XCTAssertEqual(try? coreDataStack.rootContext.count(for: countFRq), 0, "Unexpected employee entities") 74 | 75 | coreDataStack.backgroundContext.perform(block: { (context) -> CommitAction in 76 | NSEntityDescription.insertNewObject(forEntityName: "Employee", into: context) 77 | return .saveToParentContext 78 | }, completionHandler: { (result) -> Void in 79 | do { 80 | _ = try result() 81 | XCTFail("Expected error") 82 | } 83 | catch CoreDataKitError.coreDataError(let error) { 84 | XCTAssertEqual((error as NSError).code, 1570, "Incorrect error code") 85 | XCTAssertEqual(try? self.coreDataStack.rootContext.count(for: countFRq), 0, "Unexpected employee entities") 86 | completionExpectation.fulfill() 87 | } 88 | catch { 89 | XCTFail("Unexpected error") 90 | } 91 | }) 92 | 93 | waitForExpectations(timeout: 3, handler: nil) 94 | } 95 | 96 | // MARK: Obtaining permanent IDs 97 | 98 | func testObtainPermanentIDsForInsertedObjects() { 99 | let employee: Employee = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: coreDataStack.rootContext) as! Employee 100 | employee.name = "Harvey Specter" 101 | 102 | XCTAssertTrue(employee.objectID.isTemporaryID, "Object ID must be temporary") 103 | 104 | do { 105 | try coreDataStack.rootContext.obtainPermanentIDsForInsertedObjects() 106 | XCTAssertFalse(employee.objectID.isTemporaryID, "Object ID must be permanent") 107 | } 108 | catch { 109 | XCTFail("Unexpected error") 110 | } 111 | } 112 | 113 | private func testContextObtainsPermanentIDs(context: NSManagedObjectContext) { 114 | let saveExpectation = expectation(description: "Await save result") 115 | 116 | var employee: Employee! 117 | context.perform(block: { context in 118 | employee = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: context) as! Employee 119 | employee.name = "Harvey Specter" 120 | 121 | XCTAssertTrue(employee.objectID.isTemporaryID, "Object ID must be temporary") 122 | 123 | return .saveToParentContext 124 | }, completionHandler: { _ in 125 | XCTAssertFalse(employee.objectID.isTemporaryID, "Object ID must be permanent") 126 | saveExpectation.fulfill() 127 | }) 128 | 129 | waitForExpectations(timeout: 3, handler: nil) 130 | } 131 | 132 | // MARK: - Creating 133 | 134 | func testCreate() { 135 | do { 136 | let employee = try coreDataStack.rootContext.create(Employee.self) 137 | XCTAssertTrue(employee.isInserted, "Managed object should be inserted") 138 | XCTAssertEqual(employee.managedObjectContext!, coreDataStack.rootContext, "Unexpected managed object context") 139 | } 140 | catch { 141 | XCTFail("Unexpected error") 142 | } 143 | } 144 | 145 | func testCreateIncorrectEntityName() { 146 | do { 147 | _ = try coreDataStack.rootContext.create(EmployeeIncorrectEntityName.self) 148 | XCTFail("Unexpected managed object") 149 | } 150 | catch CoreDataKitError.contextError { 151 | // Expected error 152 | } 153 | catch { 154 | XCTFail("Unexpected error") 155 | } 156 | } 157 | 158 | // MARK: - Finding 159 | 160 | func testFindAllEmployeesWithOneEmployeeInserted() { 161 | do { 162 | let employee = try coreDataStack.rootContext.create(Employee.self) 163 | employee.name = "Rachel Zane" 164 | 165 | let results = try coreDataStack.rootContext.find(Employee.self) 166 | XCTAssertEqual(results.count, 1, "Incorrect number of results") 167 | 168 | if let firstEmployee = results.first { 169 | XCTAssertEqual(firstEmployee.name, "Rachel Zane", "Incorrect employee name") 170 | } 171 | } 172 | catch { 173 | XCTFail("Unexpected error") 174 | } 175 | } 176 | 177 | func testFindEmployeesWithoutAnythingInserted() { 178 | do { 179 | let results = try coreDataStack.rootContext.find(Employee.self, predicate: nil, sortDescriptors: nil, limit: nil) 180 | XCTAssertEqual(results.count, 0, "Incorrect number of results") 181 | } 182 | catch { 183 | XCTFail("Unexpected error") 184 | } 185 | } 186 | 187 | func testFindEmployeesWithFilteringAndSorting() { 188 | do { 189 | let employee0 = try coreDataStack.rootContext.create(Employee.self) 190 | let employee1 = try coreDataStack.rootContext.create(Employee.self) 191 | let employee2 = try coreDataStack.rootContext.create(Employee.self) 192 | 193 | employee0.name = "Rachel Zane 2" 194 | employee1.name = "Rachel Zane 1" 195 | employee2.name = "Mike Ross" 196 | } 197 | catch { 198 | XCTFail("Missing managed object") 199 | } 200 | 201 | let predicate = NSPredicate(format: "name contains %@", argumentArray: ["Rachel Zane"]) 202 | let sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)] 203 | 204 | do { 205 | let results = try coreDataStack.rootContext.find(Employee.self, predicate: predicate, sortDescriptors: sortDescriptors, limit: nil) 206 | 207 | XCTAssertEqual(results.count, 2, "Incorrect number of results") 208 | XCTAssertEqual(results[0].name, "Rachel Zane 1", "Incorrect order") 209 | XCTAssertEqual(results[1].name, "Rachel Zane 2", "Incorrect order") 210 | } 211 | catch { 212 | XCTFail("Unexpected error") 213 | } 214 | } 215 | } 216 | 217 | -------------------------------------------------------------------------------- /CoreDataKitTests/NSPersistentStoreCoordinatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSPersistentStoreCoordinatorTests.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 16-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import CoreData 11 | import CoreDataKit 12 | 13 | class NSPersistentStoreCoordinatorTests: TestCase { 14 | func testInitializationSQLiteStore() { 15 | let optionalCoordinator = NSPersistentStoreCoordinator(automigrating: true) 16 | XCTAssertNotNil(optionalCoordinator, "Missing coordinator") 17 | 18 | if let coordinator = optionalCoordinator { 19 | XCTAssertTrue(coordinator.persistentStores.count > 0, "Missing persistent store") 20 | } 21 | } 22 | 23 | func testInitializationMemoryStore() { 24 | let optionalCoordinator = NSPersistentStoreCoordinator(automigrating: true) 25 | XCTAssertNotNil(optionalCoordinator, "Missing coordinator") 26 | 27 | if let coordinator = optionalCoordinator { 28 | XCTAssertTrue(coordinator.persistentStores.count > 0, "Missing persistent store") 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /CoreDataKitTests/NSPersistentStoreTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSPersistentStoreTests.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 16-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import CoreData 11 | import CoreDataKit 12 | 13 | class NSPersistentStoreTests: TestCase { 14 | func testURLForSQLiteStoreName() { 15 | XCTAssertNotNil(NSPersistentStore.url(forSQLiteStoreName: "SuitsStore"), "Store URL missing") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CoreDataKitTests/TestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestCase.swift 3 | // CoreDataKit 4 | // 5 | // Created by Mathijs Kadijk on 15-10-14. 6 | // Copyright (c) 2014 Mathijs Kadijk. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import CoreData 11 | import CoreDataKit 12 | 13 | 14 | var sharedManagedObjectModel: NSManagedObjectModel = { 15 | return NSManagedObjectModel.mergedModel(from: Bundle.allBundles)! 16 | }() 17 | 18 | class TestCase: XCTestCase { 19 | 20 | lazy var sharedStack: CoreDataStack = { 21 | let stack = self.setupCoreDataStack(model: sharedManagedObjectModel) 22 | 23 | return stack 24 | }() 25 | 26 | var coreDataStack: CoreDataStack! 27 | 28 | override func setUp() { 29 | super.setUp() 30 | 31 | // Fetch model 32 | let managedObjectModel = sharedManagedObjectModel 33 | 34 | // Setup the shared stack 35 | CDK.sharedStack = sharedStack 36 | 37 | // Setup the stack for this test 38 | coreDataStack = setupCoreDataStack(model: managedObjectModel) 39 | } 40 | 41 | override func tearDown() { 42 | // Put teardown code here. This method is called after the invocation of each test method in the class. 43 | super.tearDown() 44 | } 45 | 46 | private func setupCoreDataStack(model: NSManagedObjectModel) -> CoreDataStack { 47 | let persistentCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model) 48 | try! persistentCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil) 49 | return CoreDataStack(persistentStoreCoordinator: persistentCoordinator) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Mathijs Kadijk 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2016-04-09: End of life 2 | 3 | CoreDataKit is no longer being updated with new features. It is used in several projects, and probably will be updated with new Swift versions. But it is no longer actively developed. 4 | 5 | ## CoreDataKit 6 | 7 | **CoreDataKit takes care of the hard and verbose parts of CoreData. It manages child contexts for you and helps to easily fetch, create and delete objects.** 8 | 9 | > *CoreData:* object graph management solution, including persistence. *Kit:* set of equipment needed for a specific purpose. 10 | 11 | ## Installation 12 | 13 | [CocoaPods](http://cocoapods.org) is the advised way to include CoreDataKit into your project. A basic [Podfile](http://cocoapods.org/#get_started) including CoreDataKit would look like this: 14 | 15 | ```ruby 16 | source 'https://github.com/CocoaPods/Specs.git' 17 | platform :ios, '8.0' 18 | use_frameworks! 19 | 20 | pod 'CoreDataKit' 21 | ``` 22 | 23 | ### Version guide 24 | 25 | - Swift 4: Use 0.13 and up 26 | - Swift 3: Use 0.12 27 | - Swift 2.3: Use 0.11 28 | - Swift 2.2: Use 0.10 29 | 30 | ## Usage 31 | 32 | The most basic and most used variant to setup a stack backed by an automigrating SQLite store is this: 33 | ```swift 34 | // Initialize CoreData stack 35 | if let persistentStoreCoordinator = NSPersistentStoreCoordinator(automigrating: true) { 36 | CDK.sharedStack = CoreDataStack(persistentStoreCoordinator: persistentStoreCoordinator) 37 | } 38 | ``` 39 | 40 | ### Implement NamedManagedObject protocol 41 | 42 | For CoreDataKit to be able to use your NSManagedObject subclass, such as Car in the example below, CDK needs to have the subclass implement the NamedManagedObject protocol in order to be able to initialize your class: 43 | 44 | ```swift 45 | class Car: NSManagedObject, NamedManagedObject { 46 | 47 | static var entityName = "Car" // corresponding to your Entity name in your xcdatamodeld 48 | 49 | @NSManaged var color: String 50 | @NSManaged var model: String 51 | } 52 | 53 | ``` 54 | 55 | From here you are able to use the shared stack. For example to create and save an entity, this example performs a block an a background context, saves it to the persistent store and executes a completion handler: 56 | ```swift 57 | CDK.performOnBackgroundContext(block: { context in 58 | do { 59 | let car = try context.create(Car.self) 60 | car.color = "Hammerhead Silver" 61 | car.model = "Aston Martin DB9" 62 | 63 | return .saveToPersistentStore 64 | } 65 | catch { 66 | return .doNothing 67 | } 68 | }, completionHandler: { result in 69 | do { 70 | try result() 71 | print("Car saved, time to update the interface!") 72 | } 73 | catch { 74 | print("Saving Harvey Specters car failed with error: \(error)") 75 | } 76 | }) 77 | ``` 78 | 79 | ### Using promises 80 | 81 | If you prefer using promises, instead of the callback style of this library, you can use the [Promissum](https://github.com/tomlokhorst/Promissum) library with CoreDataKit. Using the [CoreDataKit+Promise](https://github.com/tomlokhorst/Promissum/blob/develop/extensions/PromissumExtensions/CoreDataKit%2BPromise.swift) extension, the example from above can be rewritten as such: 82 | ```swift 83 | let createPromise = CDK.performOnBackgroundContextPromise { context in 84 | do { 85 | let car = try context.create(Car.self) 86 | car.color = "Hammerhead Silver" 87 | car.model = "Aston Martin DB9" 88 | 89 | return .saveToPersistentStore 90 | } 91 | catch { 92 | return .doNothing 93 | } 94 | } 95 | 96 | createPromise 97 | .then { _ in 98 | print("Car saved, time to update the interface!") 99 | } 100 | .trap { error in 101 | print("Saving Harvey Specters car failed with error: \(error)") 102 | } 103 | ``` 104 | 105 | ## Contributing 106 | 107 | We'll love contributions, please report bugs in the issue tracker, create pull request (please branch of `develop`) and suggest new great features (also in the issue tracker). 108 | 109 | ## License & Credits 110 | 111 | CoreDataKit is written by [Mathijs Kadijk](https://github.com/mac-cain13) and available under the [MIT license](LICENSE), so feel free to use it in commercial and non-commercial projects. CoreDataKit is inspired on [MagicalRecord](https://github.com/magicalpanda/MagicalRecord). 112 | --------------------------------------------------------------------------------